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