• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.bluetooth.btservice;
18 
19 import static org.mockito.Mockito.*;
20 
21 import android.bluetooth.BluetoothA2dp;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothHeadset;
25 import android.bluetooth.BluetoothProfile;
26 import android.bluetooth.BluetoothUuid;
27 import android.content.Intent;
28 import android.os.HandlerThread;
29 import android.os.ParcelUuid;
30 
31 import androidx.test.filters.MediumTest;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import com.android.bluetooth.TestUtils;
35 import com.android.bluetooth.a2dp.A2dpService;
36 import com.android.bluetooth.hfp.HeadsetService;
37 
38 import org.junit.After;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.mockito.Mock;
43 import org.mockito.MockitoAnnotations;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 
48 @MediumTest
49 @RunWith(AndroidJUnit4.class)
50 public class PhonePolicyTest {
51     private static final int MAX_CONNECTED_AUDIO_DEVICES = 5;
52     private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
53     private static final int CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS = 1000;
54     private static final int CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS =
55             CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS * 3 / 2;
56 
57     private HandlerThread mHandlerThread;
58     private BluetoothAdapter mAdapter;
59     private PhonePolicy mPhonePolicy;
60 
61     @Mock private AdapterService mAdapterService;
62     @Mock private ServiceFactory mServiceFactory;
63     @Mock private HeadsetService mHeadsetService;
64     @Mock private A2dpService mA2dpService;
65 
66     @Before
setUp()67     public void setUp() throws Exception {
68         MockitoAnnotations.initMocks(this);
69         // Stub A2DP and HFP
70         when(mHeadsetService.connect(any(BluetoothDevice.class))).thenReturn(true);
71         when(mA2dpService.connect(any(BluetoothDevice.class))).thenReturn(true);
72         // Prepare the TestUtils
73         TestUtils.setAdapterService(mAdapterService);
74         // Configure the maximum connected audio devices
75         doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
76         // Setup the mocked factory to return mocked services
77         doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService();
78         doReturn(mA2dpService).when(mServiceFactory).getA2dpService();
79         // Start handler thread for this test
80         mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread");
81         mHandlerThread.start();
82         // Mock the looper
83         doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper();
84         // Tell the AdapterService that it is a mock (see isMock documentation)
85         doReturn(true).when(mAdapterService).isMock();
86         // Must be called to initialize services
87         mAdapter = BluetoothAdapter.getDefaultAdapter();
88         PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS;
89         mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory);
90     }
91 
92     @After
tearDown()93     public void tearDown() throws Exception {
94         mHandlerThread.quit();
95         TestUtils.clearAdapterService(mAdapterService);
96     }
97 
98     /**
99      * Test that when new UUIDs are refreshed for a device then we set the priorities for various
100      * profiles accurately. The following profiles should have ON priorities:
101      *     A2DP, HFP, HID and PAN
102      */
103     @Test
testProcessInitProfilePriorities()104     public void testProcessInitProfilePriorities() {
105         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
106         // Mock the HeadsetService to return undefined priority
107         when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
108 
109         // Mock the A2DP service to return undefined priority
110         when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
111 
112         // Inject an event for UUIDs updated for a remote device with only HFP enabled
113         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
114         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
115         ParcelUuid[] uuids = new ParcelUuid[2];
116         uuids[0] = BluetoothUuid.Handsfree;
117         uuids[1] = BluetoothUuid.AudioSink;
118         intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
119         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
120 
121         // Check that the priorities of the devices for preferred profiles are set to ON
122         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device),
123                 eq(BluetoothProfile.PRIORITY_ON));
124         verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device),
125                 eq(BluetoothProfile.PRIORITY_ON));
126     }
127 
128     /**
129      * Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and
130      * A2DP enabled. NOTE that the assumption is that we have already done the pairing previously
131      * and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as
132      * part of post pairing process).
133      */
134     @Test
testAdapterOnAutoConnect()135     public void testAdapterOnAutoConnect() {
136         // Return desired values from the mocked object(s)
137         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
138         when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
139 
140         // Return a list of bonded devices (just one)
141         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
142         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
143         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
144 
145         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP
146         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
147                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
148         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
149                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
150 
151         // Inject an event that the adapter is turned on.
152         Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
153         intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
154         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
155 
156         // Check that we got a request to connect over HFP and A2DP
157         verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0]));
158         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0]));
159     }
160 
161     /**
162      * Test that when an auto connect device is disconnected, its priority is set to ON if its
163      * original priority is auto connect
164      */
165     @Test
testDisconnectNoAutoConnect()166     public void testDisconnectNoAutoConnect() {
167         // Return desired values from the mocked object(s)
168         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
169         when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
170 
171         // Return a list of bonded devices (just one)
172         BluetoothDevice[] bondedDevices = new BluetoothDevice[4];
173         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
174         bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
175         bondedDevices[2] = TestUtils.getTestDevice(mAdapter, 2);
176         bondedDevices[3] = TestUtils.getTestDevice(mAdapter, 3);
177         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
178 
179         // Make all devices auto connect
180         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
181                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
182         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
183                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
184         when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn(
185                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
186         when(mHeadsetService.getPriority(bondedDevices[3])).thenReturn(
187                 BluetoothProfile.PRIORITY_OFF);
188 
189         // Make one of the device active
190         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
191         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
192         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
193         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
194 
195         // All other disconnected device's priority is set to ON, except disabled ones
196         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0],
197                 BluetoothProfile.PRIORITY_ON);
198         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1],
199                 BluetoothProfile.PRIORITY_ON);
200         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[2],
201                 BluetoothProfile.PRIORITY_ON);
202         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0],
203                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
204         verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt());
205         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
206                 BluetoothProfile.PRIORITY_ON);
207         when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn(
208                 BluetoothProfile.PRIORITY_ON);
209 
210         // Make another device active
211         when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
212                 BluetoothProfile.STATE_CONNECTED);
213         intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
214         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
215         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
216         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
217 
218         // This device should be set to auto connect while the first device is reset to ON
219         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
220                 bondedDevices[0], BluetoothProfile.PRIORITY_ON);
221         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1],
222                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
223         verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt());
224         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
225                 BluetoothProfile.PRIORITY_ON);
226         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
227                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
228 
229         // Set active device to null
230         when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
231                 BluetoothProfile.STATE_DISCONNECTED);
232         intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
233         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) null);
234         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
235         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
236 
237         // Verify that the priority of previous active device won't be changed while active device
238         // set to null
239         verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
240                 bondedDevices[1], BluetoothProfile.PRIORITY_ON);
241         verify(mHeadsetService).setPriority(bondedDevices[1],
242                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
243         verify(mHeadsetService, never()).setPriority(bondedDevices[1],
244                 BluetoothProfile.PRIORITY_OFF);
245 
246         // Make the current active device fail to connect
247         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
248                 BluetoothProfile.STATE_DISCONNECTED);
249         updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
250                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
251 
252         // This device should be set to ON
253         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
254                 bondedDevices[1], BluetoothProfile.PRIORITY_ON);
255 
256         // Verify that we are not setting priorities to random devices and values
257         verify(mHeadsetService, times(7)).setPriority(any(BluetoothDevice.class), anyInt());
258     }
259 
260     /**
261      * Test that we will try to re-connect to a profile on a device if other profile(s) are
262      * connected. This is to add robustness to the connection mechanism
263      */
264     @Test
testReconnectOnPartialConnect()265     public void testReconnectOnPartialConnect() {
266         // Return a list of bonded devices (just one)
267         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
268         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
269         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
270 
271         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
272         // auto-connectable.
273         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
274                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
275         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
276                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
277 
278         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
279 
280         // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
281         // To enable that we need to make sure that HeadsetService returns the device as list of
282         // connected devices
283         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
284         hsConnectedDevices.add(bondedDevices[0]);
285         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
286         // Also the A2DP should say that its not connected for same device
287         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
288                 BluetoothProfile.STATE_DISCONNECTED);
289 
290         // We send a connection successful for one profile since the re-connect *only* works if we
291         // have already connected successfully over one of the profiles
292         updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
293                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
294 
295         // Check that we get a call to A2DP connect
296         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
297                 eq(bondedDevices[0]));
298     }
299 
300     /**
301      * Test that we will try to re-connect to a profile on a device next time if a previous attempt
302      * failed partially. This will make sure the connection mechanism still works at next try while
303      * the previous attempt is some profiles connected on a device but some not.
304      */
305     @Test
testReconnectOnPartialConnect_PreviousPartialFail()306     public void testReconnectOnPartialConnect_PreviousPartialFail() {
307         // Return a list of bonded devices (just one)
308         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
309         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
310         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
311 
312         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
313         // auto-connectable.
314         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
315                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
316         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
317                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
318 
319         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
320 
321         // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
322         // To enable that we need to make sure that HeadsetService returns the device among a list
323         // of connected devices
324         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
325         hsConnectedDevices.add(bondedDevices[0]);
326         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
327         // Also the A2DP should say that its not connected for same device
328         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
329                 BluetoothProfile.STATE_DISCONNECTED);
330 
331         // We send a connection success event for one profile since the re-connect *only* works if
332         // we have already connected successfully over one of the profiles
333         updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
334                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
335 
336         // Check that we get a call to A2DP reconnect
337         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
338                 bondedDevices[0]);
339 
340         // We send a connection failure event for the attempted profile, and keep the connected
341         // profile connected.
342         updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.A2DP,
343                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
344 
345         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
346 
347         // Verify no one changes the priority of the failed profile
348         verify(mA2dpService, never()).setPriority(eq(bondedDevices[0]), anyInt());
349 
350         // Send a connection success event for one profile again without disconnecting all profiles
351         updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
352                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
353 
354         // Check that we won't get a call to A2DP reconnect again before all profiles disconnected
355         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
356                 bondedDevices[0]);
357 
358         // Send a disconnection event for all connected profiles
359         hsConnectedDevices.remove(bondedDevices[0]);
360         updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
361                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
362 
363         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
364 
365         // Send a connection success event for one profile again to trigger re-connect
366         hsConnectedDevices.add(bondedDevices[0]);
367         updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
368                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
369 
370         // Check that we get a call to A2DP connect again
371         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect(
372                 bondedDevices[0]);
373     }
374 
375     /**
376      * Test that a second device will auto-connect if there is already one connected device.
377      *
378      * Even though we currently only set one device to be auto connect. The consumer of the auto
379      * connect property works independently so that we will connect to all devices that are in
380      * auto connect mode.
381      */
382     @Test
testAutoConnectMultipleDevices()383     public void testAutoConnectMultipleDevices() {
384         final int kMaxTestDevices = 3;
385         BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
386         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
387         ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
388         BluetoothDevice a2dpNotConnectedDevice1 = null;
389         BluetoothDevice a2dpNotConnectedDevice2 = null;
390 
391         for (int i = 0; i < kMaxTestDevices; i++) {
392             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
393             testDevices[i] = testDevice;
394 
395             // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles
396             // are auto-connectable.
397             when(mHeadsetService.getPriority(testDevice)).thenReturn(
398                     BluetoothProfile.PRIORITY_AUTO_CONNECT);
399             when(mA2dpService.getPriority(testDevice)).thenReturn(
400                     BluetoothProfile.PRIORITY_AUTO_CONNECT);
401             // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
402             // To enable that we need to make sure that HeadsetService returns the device as list
403             // of connected devices.
404             hsConnectedDevices.add(testDevice);
405             // Connect A2DP for all devices except the last one
406             if (i < (kMaxTestDevices - 2)) {
407                 a2dpConnectedDevices.add(testDevice);
408             }
409         }
410         a2dpNotConnectedDevice1 = hsConnectedDevices.get(kMaxTestDevices - 1);
411         a2dpNotConnectedDevice2 = hsConnectedDevices.get(kMaxTestDevices - 2);
412 
413         when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
414         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
415         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
416         when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
417         // Two of the A2DP devices are not connected
418         when(mA2dpService.getConnectionState(a2dpNotConnectedDevice1)).thenReturn(
419                 BluetoothProfile.STATE_DISCONNECTED);
420         when(mA2dpService.getConnectionState(a2dpNotConnectedDevice2)).thenReturn(
421                 BluetoothProfile.STATE_DISCONNECTED);
422 
423         // We send a connection successful for one profile since the re-connect *only* works if we
424         // have already connected successfully over one of the profiles
425         updateProfileConnectionStateHelper(a2dpNotConnectedDevice1, BluetoothProfile.HEADSET,
426                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
427 
428         // We send a connection successful for one profile since the re-connect *only* works if we
429         // have already connected successfully over one of the profiles
430         updateProfileConnectionStateHelper(a2dpNotConnectedDevice2, BluetoothProfile.HEADSET,
431                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
432 
433         // Check that we get a call to A2DP connect
434         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
435                 eq(a2dpNotConnectedDevice1));
436         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
437                 eq(a2dpNotConnectedDevice2));
438     }
439 
440     /**
441      * Test that the connect priority of all devices are set as appropriate if there is one
442      * connected device.
443      * - The HFP and A2DP connect priority for connected devices is set to
444      *   BluetoothProfile.PRIORITY_AUTO_CONNECT
445      * - The HFP and A2DP connect priority for bonded devices is set to
446      *   BluetoothProfile.PRIORITY_ON
447      */
448     @Test
testSetPriorityMultipleDevices()449     public void testSetPriorityMultipleDevices() {
450         // testDevices[0] - connected for both HFP and A2DP
451         // testDevices[1] - connected only for HFP - will auto-connect for A2DP
452         // testDevices[2] - connected only for A2DP - will auto-connect for HFP
453         // testDevices[3] - not connected
454         final int kMaxTestDevices = 4;
455         BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
456         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
457         ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
458 
459         for (int i = 0; i < kMaxTestDevices; i++) {
460             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
461             testDevices[i] = testDevice;
462 
463             // Connect HFP and A2DP for each device as appropriate.
464             // Return PRIORITY_AUTO_CONNECT only for testDevices[0]
465             if (i == 0) {
466                 hsConnectedDevices.add(testDevice);
467                 a2dpConnectedDevices.add(testDevice);
468                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
469                         BluetoothProfile.PRIORITY_AUTO_CONNECT);
470                 when(mA2dpService.getPriority(testDevice)).thenReturn(
471                         BluetoothProfile.PRIORITY_AUTO_CONNECT);
472             }
473             if (i == 1) {
474                 hsConnectedDevices.add(testDevice);
475                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
476                         BluetoothProfile.PRIORITY_ON);
477                 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
478             }
479             if (i == 2) {
480                 a2dpConnectedDevices.add(testDevice);
481                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
482                         BluetoothProfile.PRIORITY_ON);
483                 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
484             }
485             if (i == 3) {
486                 // Device not connected
487                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
488                         BluetoothProfile.PRIORITY_ON);
489                 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
490             }
491         }
492         when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
493         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
494         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
495         when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
496         // Some of the devices are not connected
497         // testDevices[0] - connected for both HFP and A2DP
498         when(mHeadsetService.getConnectionState(testDevices[0])).thenReturn(
499                 BluetoothProfile.STATE_CONNECTED);
500         when(mA2dpService.getConnectionState(testDevices[0])).thenReturn(
501                 BluetoothProfile.STATE_CONNECTED);
502         // testDevices[1] - connected only for HFP - will auto-connect for A2DP
503         when(mHeadsetService.getConnectionState(testDevices[1])).thenReturn(
504                 BluetoothProfile.STATE_CONNECTED);
505         when(mA2dpService.getConnectionState(testDevices[1])).thenReturn(
506                 BluetoothProfile.STATE_DISCONNECTED);
507         // testDevices[2] - connected only for A2DP - will auto-connect for HFP
508         when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn(
509                 BluetoothProfile.STATE_DISCONNECTED);
510         when(mA2dpService.getConnectionState(testDevices[2])).thenReturn(
511                 BluetoothProfile.STATE_CONNECTED);
512         // testDevices[3] - not connected
513         when(mHeadsetService.getConnectionState(testDevices[3])).thenReturn(
514                 BluetoothProfile.STATE_DISCONNECTED);
515         when(mA2dpService.getConnectionState(testDevices[3])).thenReturn(
516                 BluetoothProfile.STATE_DISCONNECTED);
517 
518 
519         // Generate connection state changed for HFP for testDevices[1] and trigger
520         // auto-connect for A2DP.
521         updateProfileConnectionStateHelper(testDevices[1], BluetoothProfile.HEADSET,
522                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
523 
524         // Check that we get a call to A2DP connect
525         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
526                 eq(testDevices[1]));
527 
528         // testDevices[1] auto-connect completed for A2DP
529         a2dpConnectedDevices.add(testDevices[1]);
530         when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
531         when(mA2dpService.getConnectionState(testDevices[1])).thenReturn(
532                 BluetoothProfile.STATE_CONNECTED);
533 
534         // Check the connect priorities for all devices
535         // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
536         // - testDevices[1] - connection state changed for HFP should no longer trigger auto
537         //                    connect priority change since it is now triggered by A2DP active
538         //                    device change intent
539         // - testDevices[2] - connected for A2DP: setPriority() should not be called
540         // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
541         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
542         verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
543         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]),
544                 eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
545         verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
546         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
547         verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]), anyInt());
548         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
549         verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
550         clearInvocations(mHeadsetService, mA2dpService);
551 
552         // Generate connection state changed for A2DP for testDevices[2] and trigger
553         // auto-connect for HFP.
554         updateProfileConnectionStateHelper(testDevices[2], BluetoothProfile.A2DP,
555                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
556 
557         // Check that we get a call to HFP connect
558         verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
559                 eq(testDevices[2]));
560 
561         // testDevices[2] auto-connect completed for HFP
562         hsConnectedDevices.add(testDevices[2]);
563         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
564         when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn(
565                 BluetoothProfile.STATE_CONNECTED);
566 
567         // Check the connect priorities for all devices
568         // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
569         // - testDevices[1] - connected for HFP and A2DP: setPriority() should not be called
570         // - testDevices[2] - connection state changed for A2DP should no longer trigger auto
571         //                    connect priority change since it is now triggered by A2DP
572         //                    active device change intent
573         // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
574         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
575         verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
576         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]), anyInt());
577         verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
578         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
579         verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]),
580                 eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
581         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
582         verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
583         clearInvocations(mHeadsetService, mA2dpService);
584     }
585 
586     /**
587      * Test that we will not try to reconnect on a profile if all the connections failed
588      */
589     @Test
testNoReconnectOnNoConnect()590     public void testNoReconnectOnNoConnect() {
591         // Return a list of bonded devices (just one)
592         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
593         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
594         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
595 
596         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
597         // auto-connectable.
598         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
599                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
600         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
601                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
602 
603         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
604 
605         // Return an empty list simulating that the above connection successful was nullified
606         when(mHeadsetService.getConnectedDevices()).thenReturn(Collections.emptyList());
607         when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList());
608 
609         // Both A2DP and HFP should say this device is not connected, except for the intent
610         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
611                 BluetoothProfile.STATE_DISCONNECTED);
612         when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
613                 BluetoothProfile.STATE_DISCONNECTED);
614 
615         // We send a connection successful for one profile since the re-connect *only* works if we
616         // have already connected successfully over one of the profiles
617         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
618         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
619         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
620         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
621         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
622         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
623 
624         // Check that we don't get any calls to reconnect
625         verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
626                 eq(bondedDevices[0]));
627         verify(mHeadsetService, never()).connect(eq(bondedDevices[0]));
628     }
629 
630     /**
631      * Test that we will not try to reconnect on a profile if all the connections failed
632      * with multiple devices
633      */
634     @Test
testNoReconnectOnNoConnect_MultiDevice()635     public void testNoReconnectOnNoConnect_MultiDevice() {
636         // Return a list of bonded devices (just one)
637         BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
638         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
639         bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
640         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
641 
642         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
643         // auto-connectable.
644         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
645                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
646         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
647                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
648         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
649                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
650         when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
651                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
652 
653         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
654 
655         // Return an a list with only the second device as connected
656         when(mHeadsetService.getConnectedDevices()).thenReturn(
657                 Collections.singletonList(bondedDevices[1]));
658         when(mA2dpService.getConnectedDevices()).thenReturn(
659                 Collections.singletonList(bondedDevices[1]));
660 
661         // Both A2DP and HFP should say this device is not connected, except for the intent
662         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
663                 BluetoothProfile.STATE_DISCONNECTED);
664         when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
665                 BluetoothProfile.STATE_DISCONNECTED);
666         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
667                 BluetoothProfile.STATE_CONNECTED);
668         when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
669                 BluetoothProfile.STATE_CONNECTED);
670 
671         // We send a connection successful for one profile since the re-connect *only* works if we
672         // have already connected successfully over one of the profiles
673         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
674         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
675         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
676         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
677         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
678         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
679 
680         // Check that we don't get any calls to reconnect
681         verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
682                 eq(bondedDevices[0]));
683         verify(mHeadsetService, never()).connect(eq(bondedDevices[0]));
684     }
685 
686     /**
687      * Test that we will try to connect to other profiles of a device if it is partially connected
688      */
689     @Test
testReconnectOnPartialConnect_MultiDevice()690     public void testReconnectOnPartialConnect_MultiDevice() {
691         // Return a list of bonded devices (just one)
692         BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
693         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
694         bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
695         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
696 
697         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
698         // auto-connectable.
699         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
700                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
701         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
702                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
703         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
704                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
705         when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
706                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
707 
708         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
709 
710         // Return an a list with only the second device as connected
711         when(mHeadsetService.getConnectedDevices()).thenReturn(
712                 Collections.singletonList(bondedDevices[1]));
713         when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList());
714 
715         // Both A2DP and HFP should say this device is not connected, except for the intent
716         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
717                 BluetoothProfile.STATE_DISCONNECTED);
718         when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
719                 BluetoothProfile.STATE_DISCONNECTED);
720         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
721                 BluetoothProfile.STATE_DISCONNECTED);
722 
723         // We send a connection successful for one profile since the re-connect *only* works if we
724         // have already connected successfully over one of the profiles
725         updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
726                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
727 
728         // Check that we don't get any calls to reconnect
729         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
730                 eq(bondedDevices[1]));
731     }
732 
733     /**
734      * Test that a device with no supported uuids is initialized properly and does not crash the
735      * stack
736      */
737     @Test
testNoSupportedUuids()738     public void testNoSupportedUuids() {
739         // Mock the HeadsetService to return undefined priority
740         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
741         when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
742 
743         // Mock the A2DP service to return undefined priority
744         when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
745 
746         // Inject an event for UUIDs updated for a remote device with only HFP enabled
747         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
748         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
749 
750         // Put no UUIDs
751         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
752 
753         // Check that we do not crash and not call any setPriority methods
754         verify(mHeadsetService,
755                 after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).setPriority(eq(device),
756                 eq(BluetoothProfile.PRIORITY_ON));
757         verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
758     }
759 
updateProfileConnectionStateHelper(BluetoothDevice device, int profileId, int nextState, int prevState)760     private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId,
761             int nextState, int prevState) {
762         Intent intent;
763         switch (profileId) {
764             case BluetoothProfile.A2DP:
765                 when(mA2dpService.getConnectionState(device)).thenReturn(nextState);
766                 intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
767                 break;
768             case BluetoothProfile.HEADSET:
769                 when(mHeadsetService.getConnectionState(device)).thenReturn(nextState);
770                 intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
771                 break;
772             default:
773                 intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
774                 break;
775         }
776         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
777         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
778         intent.putExtra(BluetoothProfile.EXTRA_STATE, nextState);
779         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
780         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
781     }
782 }
783