• 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.BroadcastReceiver;
28 import android.content.Intent;
29 import android.os.HandlerThread;
30 import android.os.ParcelUuid;
31 import android.support.test.filters.MediumTest;
32 import android.support.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         // This should not have any effect
238         verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
239                 bondedDevices[1], BluetoothProfile.PRIORITY_ON);
240 
241         // Make the current active device fail to connect
242         when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
243                 BluetoothProfile.STATE_DISCONNECTED);
244         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
245                 BluetoothProfile.STATE_DISCONNECTED);
246         intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
247         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
248         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
249         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
250         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
251         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
252 
253         // This device should be set to ON
254         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
255                 bondedDevices[1], BluetoothProfile.PRIORITY_ON);
256 
257         // Verify that we are not setting priorities to random devices and values
258         verify(mHeadsetService, times(7)).setPriority(any(BluetoothDevice.class), anyInt());
259     }
260 
261     /**
262      * Test that we will try to re-connect to a profile on a device if an attempt failed previously.
263      * This is to add robustness to the connection mechanism
264      */
265     @Test
testReconnectOnPartialConnect()266     public void testReconnectOnPartialConnect() {
267         // Return a list of bonded devices (just one)
268         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
269         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
270         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
271 
272         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
273         // auto-connectable.
274         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
275                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
276         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
277                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
278 
279         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
280 
281         // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
282         // To enable that we need to make sure that HeadsetService returns the device as list of
283         // connected devices
284         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
285         hsConnectedDevices.add(bondedDevices[0]);
286         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
287         // Also the A2DP should say that its not connected for same device
288         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
289                 BluetoothProfile.STATE_DISCONNECTED);
290 
291         // We send a connection successful for one profile since the re-connect *only* works if we
292         // have already connected successfully over one of the profiles
293         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
294         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
295         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
296         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
297         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
298         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
299 
300         // Check that we get a call to A2DP connect
301         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
302                 eq(bondedDevices[0]));
303     }
304 
305     /**
306      * Test that a second device will auto-connect if there is already one connected device.
307      *
308      * Even though we currently only set one device to be auto connect. The consumer of the auto
309      * connect property works independently so that we will connect to all devices that are in
310      * auto connect mode.
311      */
312     @Test
testAutoConnectMultipleDevices()313     public void testAutoConnectMultipleDevices() {
314         final int kMaxTestDevices = 3;
315         BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
316         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
317         ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
318         BluetoothDevice a2dpNotConnectedDevice1 = null;
319         BluetoothDevice a2dpNotConnectedDevice2 = null;
320 
321         for (int i = 0; i < kMaxTestDevices; i++) {
322             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
323             testDevices[i] = testDevice;
324 
325             // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles
326             // are auto-connectable.
327             when(mHeadsetService.getPriority(testDevice)).thenReturn(
328                     BluetoothProfile.PRIORITY_AUTO_CONNECT);
329             when(mA2dpService.getPriority(testDevice)).thenReturn(
330                     BluetoothProfile.PRIORITY_AUTO_CONNECT);
331             // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
332             // To enable that we need to make sure that HeadsetService returns the device as list
333             // of connected devices.
334             hsConnectedDevices.add(testDevice);
335             // Connect A2DP for all devices except the last one
336             if (i < (kMaxTestDevices - 2)) {
337                 a2dpConnectedDevices.add(testDevice);
338             }
339         }
340         a2dpNotConnectedDevice1 = hsConnectedDevices.get(kMaxTestDevices - 1);
341         a2dpNotConnectedDevice2 = hsConnectedDevices.get(kMaxTestDevices - 2);
342 
343         when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
344         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
345         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
346         when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
347         // Two of the A2DP devices are not connected
348         when(mA2dpService.getConnectionState(a2dpNotConnectedDevice1)).thenReturn(
349                 BluetoothProfile.STATE_DISCONNECTED);
350         when(mA2dpService.getConnectionState(a2dpNotConnectedDevice2)).thenReturn(
351                 BluetoothProfile.STATE_DISCONNECTED);
352 
353         // We send a connection successful for one profile since the re-connect *only* works if we
354         // have already connected successfully over one of the profiles
355         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
356         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice1);
357         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
358         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
359         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
360         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
361 
362         // We send a connection successful for one profile since the re-connect *only* works if we
363         // have already connected successfully over one of the profiles
364         intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
365         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice2);
366         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
367         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
368         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
369         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
370 
371         // Check that we get a call to A2DP connect
372         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
373                 eq(a2dpNotConnectedDevice1));
374         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
375                 eq(a2dpNotConnectedDevice2));
376     }
377 
378     /**
379      * Test that the connect priority of all devices are set as appropriate if there is one
380      * connected device.
381      * - The HFP and A2DP connect priority for connected devices is set to
382      *   BluetoothProfile.PRIORITY_AUTO_CONNECT
383      * - The HFP and A2DP connect priority for bonded devices is set to
384      *   BluetoothProfile.PRIORITY_ON
385      */
386     @Test
testSetPriorityMultipleDevices()387     public void testSetPriorityMultipleDevices() {
388         // testDevices[0] - connected for both HFP and A2DP
389         // testDevices[1] - connected only for HFP - will auto-connect for A2DP
390         // testDevices[2] - connected only for A2DP - will auto-connect for HFP
391         // testDevices[3] - not connected
392         final int kMaxTestDevices = 4;
393         BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
394         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
395         ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
396 
397         for (int i = 0; i < kMaxTestDevices; i++) {
398             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
399             testDevices[i] = testDevice;
400 
401             // Connect HFP and A2DP for each device as appropriate.
402             // Return PRIORITY_AUTO_CONNECT only for testDevices[0]
403             if (i == 0) {
404                 hsConnectedDevices.add(testDevice);
405                 a2dpConnectedDevices.add(testDevice);
406                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
407                         BluetoothProfile.PRIORITY_AUTO_CONNECT);
408                 when(mA2dpService.getPriority(testDevice)).thenReturn(
409                         BluetoothProfile.PRIORITY_AUTO_CONNECT);
410             }
411             if (i == 1) {
412                 hsConnectedDevices.add(testDevice);
413                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
414                         BluetoothProfile.PRIORITY_ON);
415                 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
416             }
417             if (i == 2) {
418                 a2dpConnectedDevices.add(testDevice);
419                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
420                         BluetoothProfile.PRIORITY_ON);
421                 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
422             }
423             if (i == 3) {
424                 // Device not connected
425                 when(mHeadsetService.getPriority(testDevice)).thenReturn(
426                         BluetoothProfile.PRIORITY_ON);
427                 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
428             }
429         }
430         when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
431         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
432         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
433         when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
434         // Some of the devices are not connected
435         // testDevices[0] - connected for both HFP and A2DP
436         when(mHeadsetService.getConnectionState(testDevices[0])).thenReturn(
437                 BluetoothProfile.STATE_CONNECTED);
438         when(mA2dpService.getConnectionState(testDevices[0])).thenReturn(
439                 BluetoothProfile.STATE_CONNECTED);
440         // testDevices[1] - connected only for HFP - will auto-connect for A2DP
441         when(mHeadsetService.getConnectionState(testDevices[1])).thenReturn(
442                 BluetoothProfile.STATE_CONNECTED);
443         when(mA2dpService.getConnectionState(testDevices[1])).thenReturn(
444                 BluetoothProfile.STATE_DISCONNECTED);
445         // testDevices[2] - connected only for A2DP - will auto-connect for HFP
446         when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn(
447                 BluetoothProfile.STATE_DISCONNECTED);
448         when(mA2dpService.getConnectionState(testDevices[2])).thenReturn(
449                 BluetoothProfile.STATE_CONNECTED);
450         // testDevices[3] - not connected
451         when(mHeadsetService.getConnectionState(testDevices[3])).thenReturn(
452                 BluetoothProfile.STATE_DISCONNECTED);
453         when(mA2dpService.getConnectionState(testDevices[3])).thenReturn(
454                 BluetoothProfile.STATE_DISCONNECTED);
455 
456         // Get the broadcast receiver to inject events
457         BroadcastReceiver injector = mPhonePolicy.getBroadcastReceiver();
458 
459         // Generate connection state changed for HFP for testDevices[1] and trigger
460         // auto-connect for A2DP.
461         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
462         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[1]);
463         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
464         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
465         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
466         injector.onReceive(null /* context */, intent);
467         // Check that we get a call to A2DP connect
468         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
469                 eq(testDevices[1]));
470 
471         // testDevices[1] auto-connect completed for A2DP
472         a2dpConnectedDevices.add(testDevices[1]);
473         when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
474         when(mA2dpService.getConnectionState(testDevices[1])).thenReturn(
475                 BluetoothProfile.STATE_CONNECTED);
476 
477         // Check the connect priorities for all devices
478         // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
479         // - testDevices[1] - connection state changed for HFP should no longer trigger auto
480         //                    connect priority change since it is now triggered by A2DP active
481         //                    device change intent
482         // - testDevices[2] - connected for A2DP: setPriority() should not be called
483         // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
484         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
485         verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
486         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]),
487                 eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
488         verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
489         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
490         verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]), anyInt());
491         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
492         verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
493         clearInvocations(mHeadsetService, mA2dpService);
494 
495         // Generate connection state changed for A2DP for testDevices[2] and trigger
496         // auto-connect for HFP.
497         intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
498         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[2]);
499         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
500         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
501         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
502         injector.onReceive(null /* context */, intent);
503         // Check that we get a call to HFP connect
504         verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
505                 eq(testDevices[2]));
506 
507         // testDevices[2] auto-connect completed for HFP
508         hsConnectedDevices.add(testDevices[2]);
509         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
510         when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn(
511                 BluetoothProfile.STATE_CONNECTED);
512 
513         // Check the connect priorities for all devices
514         // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
515         // - testDevices[1] - connected for HFP and A2DP: setPriority() should not be called
516         // - testDevices[2] - connection state changed for A2DP should no longer trigger auto
517         //                    connect priority change since it is now triggered by A2DP
518         //                    active device change intent
519         // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
520         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
521         verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
522         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]), anyInt());
523         verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
524         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
525         verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]),
526                 eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
527         verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
528         verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
529         clearInvocations(mHeadsetService, mA2dpService);
530     }
531 
532     /**
533      * Test that we will not try to reconnect on a profile if all the connections failed
534      */
535     @Test
testNoReconnectOnNoConnect()536     public void testNoReconnectOnNoConnect() {
537         // Return a list of bonded devices (just one)
538         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
539         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
540         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
541 
542         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
543         // auto-connectable.
544         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
545                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
546         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
547                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
548 
549         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
550 
551         // Return an empty list simulating that the above connection successful was nullified
552         when(mHeadsetService.getConnectedDevices()).thenReturn(Collections.emptyList());
553         when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList());
554 
555         // Both A2DP and HFP should say this device is not connected, except for the intent
556         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
557                 BluetoothProfile.STATE_DISCONNECTED);
558         when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
559                 BluetoothProfile.STATE_DISCONNECTED);
560 
561         // We send a connection successful for one profile since the re-connect *only* works if we
562         // have already connected successfully over one of the profiles
563         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
564         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
565         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
566         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
567         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
568         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
569 
570         // Check that we don't get any calls to reconnect
571         verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
572                 eq(bondedDevices[0]));
573         verify(mHeadsetService, never()).connect(eq(bondedDevices[0]));
574     }
575 
576     /**
577      * Test that we will not try to reconnect on a profile if all the connections failed
578      * with multiple devices
579      */
580     @Test
testNoReconnectOnNoConnect_MultiDevice()581     public void testNoReconnectOnNoConnect_MultiDevice() {
582         // Return a list of bonded devices (just one)
583         BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
584         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
585         bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
586         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
587 
588         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
589         // auto-connectable.
590         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
591                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
592         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
593                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
594         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
595                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
596         when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
597                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
598 
599         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
600 
601         // Return an a list with only the second device as connected
602         when(mHeadsetService.getConnectedDevices()).thenReturn(
603                 Collections.singletonList(bondedDevices[1]));
604         when(mA2dpService.getConnectedDevices()).thenReturn(
605                 Collections.singletonList(bondedDevices[1]));
606 
607         // Both A2DP and HFP should say this device is not connected, except for the intent
608         when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
609                 BluetoothProfile.STATE_DISCONNECTED);
610         when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
611                 BluetoothProfile.STATE_DISCONNECTED);
612         when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
613                 BluetoothProfile.STATE_CONNECTED);
614         when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
615                 BluetoothProfile.STATE_CONNECTED);
616 
617         // We send a connection successful for one profile since the re-connect *only* works if we
618         // have already connected successfully over one of the profiles
619         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
620         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
621         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
622         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
623         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
624         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
625 
626         // Check that we don't get any calls to reconnect
627         verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
628                 eq(bondedDevices[0]));
629         verify(mHeadsetService, never()).connect(eq(bondedDevices[0]));
630     }
631 
632     /**
633      * Test that we will try to connect to other profiles of a device if it is partially connected
634      */
635     @Test
testReconnectOnPartialConnect_MultiDevice()636     public void testReconnectOnPartialConnect_MultiDevice() {
637         // Return a list of bonded devices (just one)
638         BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
639         bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
640         bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
641         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
642 
643         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
644         // auto-connectable.
645         when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
646                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
647         when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
648                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
649         when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
650                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
651         when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
652                 BluetoothProfile.PRIORITY_AUTO_CONNECT);
653 
654         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
655 
656         // Return an a list with only the second device as connected
657         when(mHeadsetService.getConnectedDevices()).thenReturn(
658                 Collections.singletonList(bondedDevices[1]));
659         when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList());
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_DISCONNECTED);
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[1]);
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, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
682                 eq(bondedDevices[1]));
683     }
684 
685     /**
686      * Test that a device with no supported uuids is initialized properly and does not crash the
687      * stack
688      */
689     @Test
testNoSupportedUuids()690     public void testNoSupportedUuids() {
691         // Mock the HeadsetService to return undefined priority
692         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
693         when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
694 
695         // Mock the A2DP service to return undefined priority
696         when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
697 
698         // Inject an event for UUIDs updated for a remote device with only HFP enabled
699         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
700         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
701 
702         // Put no UUIDs
703         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
704 
705         // Check that we do not crash and not call any setPriority methods
706         verify(mHeadsetService,
707                 after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).setPriority(eq(device),
708                 eq(BluetoothProfile.PRIORITY_ON));
709         verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
710     }
711 }
712