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 android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothClass;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothHeadset;
23 import android.bluetooth.BluetoothProfile;
24 import android.bluetooth.BluetoothUuid;
25 import android.content.BroadcastReceiver;
26 import android.content.Intent;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.ParcelUuid;
32 import android.util.Log;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.LinkedList;
36 import java.util.Queue;
37 import java.lang.Thread;
38 
39 import android.test.AndroidTestCase;
40 
41 import com.android.bluetooth.hfp.HeadsetService;
42 import com.android.bluetooth.a2dp.A2dpService;
43 import com.android.bluetooth.btservice.PhonePolicy;
44 import com.android.bluetooth.btservice.ServiceFactory;
45 import com.android.bluetooth.Utils;
46 
47 import static org.mockito.Mockito.*;
48 
49 public class PhonePolicyTest extends AndroidTestCase {
50     private static final String TAG = "PhonePolicyTest";
51     private static final int ASYNC_CALL_TIMEOUT = 2000; // 2s
52     private static final int RETRY_TIMEOUT = 10000; // 10s
53 
54     private HandlerThread mHandlerThread;
55     private BluetoothAdapter mAdapter;
56 
57     @Override
setUp()58     protected void setUp() {
59         mHandlerThread = new HandlerThread("PhonePolicyTest");
60         mHandlerThread.start();
61         mAdapter = BluetoothAdapter.getDefaultAdapter();
62     }
63 
64     @Override
tearDown()65     protected void tearDown() {
66         mHandlerThread.quit();
67     }
68 
69     // Test that when new UUIDs are refreshed for a device then we set the priorities for various
70     // profiles accurately. The following profiles should have ON priorities:
71     // A2DP, HFP, HID and PAN
testProcessInitProfilePriorities()72     public void testProcessInitProfilePriorities() {
73         BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
74         BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
75 
76         // Create the mock objects required
77         AdapterService mockAdapterService = mock(AdapterService.class);
78         ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
79         HeadsetService mockHeadsetService = mock(HeadsetService.class);
80         A2dpService mockA2dpService = mock(A2dpService.class);
81 
82         // Mock the HeadsetService
83         when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
84         when(mockHeadsetService.getPriority(device))
85                 .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
86 
87         // Mock the A2DP service
88         when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
89         when(mockA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
90 
91         // Mock the looper
92         when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
93 
94         // Tell the AdapterService that it is a mock (see isMock documentation)
95         when(mockAdapterService.isMock()).thenReturn(true);
96 
97         PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
98 
99         // Get the broadcast receiver to inject events.
100         BroadcastReceiver injector = phPol.getBroadcastReceiver();
101 
102         // Inject an event for UUIDs updated for a remote device with only HFP enabled
103         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
104         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
105         ParcelUuid[] uuids = new ParcelUuid[2];
106         uuids[0] = BluetoothUuid.Handsfree;
107         uuids[1] = BluetoothUuid.AudioSink;
108 
109         intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
110         injector.onReceive(null /* context */, intent);
111 
112         // Check that the priorities of the devices for preferred profiles are set to ON
113         verify(mockHeadsetService, timeout(ASYNC_CALL_TIMEOUT).times(1))
114                 .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
115         verify(mockA2dpService, timeout(ASYNC_CALL_TIMEOUT).times(1))
116                 .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
117     }
118 
119     // Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and
120     // A2DP enabled. NOTE that the assumption is that we have already done the pairing previously
121     // and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as
122     // part of post pairing process).
testAdapterOnAutoConnect()123     public void testAdapterOnAutoConnect() {
124         BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
125         BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
126 
127         // Create the mock objects required
128         AdapterService mockAdapterService = mock(AdapterService.class);
129         ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
130         HeadsetService mockHeadsetService = mock(HeadsetService.class);
131         A2dpService mockA2dpService = mock(A2dpService.class);
132 
133         // Return desired values from the mocked object(s)
134         when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
135         when(mockAdapterService.isQuietModeEnabled()).thenReturn(false);
136         when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
137         when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
138 
139         // Return a list of bonded devices (just one)
140         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
141         bondedDevices[0] = device;
142         when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices);
143 
144         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP
145         when(mockHeadsetService.getPriority(device))
146                 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
147         when(mockA2dpService.getPriority(device))
148                 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
149 
150         // Mock the looper
151         when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
152 
153         // Tell the AdapterService that it is a mock (see isMock documentation)
154         when(mockAdapterService.isMock()).thenReturn(true);
155 
156         PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
157 
158         // Get the broadcast receiver to inject events
159         BroadcastReceiver injector = phPol.getBroadcastReceiver();
160 
161         // Inject an event that the adapter is turned on.
162         Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
163         intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
164         injector.onReceive(null /* context */, intent);
165 
166         // Check that we got a request to connect over HFP and A2DP
167         verify(mockHeadsetService, timeout(ASYNC_CALL_TIMEOUT).times(1)).connect(eq(device));
168         verify(mockA2dpService, timeout(ASYNC_CALL_TIMEOUT).times(1)).connect(eq(device));
169     }
170 
171     // Test that we will try to re-connect to a profile on a device if an attempt failed previously.
172     // This is to add robustness to the connection mechanism
testReconnectOnPartialConnect()173     public void testReconnectOnPartialConnect() {
174         BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
175         BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
176 
177         // Create the mock objects required
178         AdapterService mockAdapterService = mock(AdapterService.class);
179         ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
180         HeadsetService mockHeadsetService = mock(HeadsetService.class);
181         A2dpService mockA2dpService = mock(A2dpService.class);
182 
183         // Setup the mocked factory to return mocked services
184         when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
185         when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
186 
187         // Return a list of bonded devices (just one)
188         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
189         bondedDevices[0] = device;
190         when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices);
191 
192         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
193         // auto-connectable.
194         when(mockHeadsetService.getPriority(device))
195                 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
196         when(mockA2dpService.getPriority(device))
197                 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
198 
199         when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
200 
201         // Mock the looper
202         when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
203 
204         // Tell the AdapterService that it is a mock (see isMock documentation)
205         when(mockAdapterService.isMock()).thenReturn(true);
206 
207         PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
208 
209         // Get the broadcast receiver to inject events
210         BroadcastReceiver injector = phPol.getBroadcastReceiver();
211 
212         // We send a connection successful for one profile since the re-connect *only* works if we
213         // have already connected successfully over one of the profiles
214         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
215         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
216         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
217         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
218         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
219         injector.onReceive(null /* context */, intent);
220 
221         // We should see (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
222         // To enable that we need to make sure that HeadsetService returns the device as list of
223         // connected devices
224         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
225         hsConnectedDevices.add(device);
226         when(mockHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
227         // Also the A2DP should say that its not connected for same device
228         when(mockA2dpService.getConnectionState(device))
229                 .thenReturn(BluetoothProfile.STATE_DISCONNECTED);
230 
231         // Check that we get a call to A2DP connect
232         verify(mockA2dpService, timeout(RETRY_TIMEOUT).times(1)).connect(eq(device));
233     }
234 
235     // Test that we will not try to reconnect on a profile if all the connections failed
testNoReconnectOnNoConnect()236     public void testNoReconnectOnNoConnect() {
237         BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
238         BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
239 
240         // Create the mock objects required
241         AdapterService mockAdapterService = mock(AdapterService.class);
242         ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
243         HeadsetService mockHeadsetService = mock(HeadsetService.class);
244         A2dpService mockA2dpService = mock(A2dpService.class);
245 
246         // Setup the mocked factory to return mocked services
247         when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
248         when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
249 
250         // Return a list of bonded devices (just one)
251         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
252         bondedDevices[0] = device;
253         when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices);
254 
255         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
256         // auto-connectable.
257         when(mockHeadsetService.getPriority(device))
258                 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
259         when(mockA2dpService.getPriority(device))
260                 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
261 
262         when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
263 
264         // Mock the looper
265         when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
266 
267         // Tell the AdapterService that it is a mock (see isMock documentation)
268         when(mockAdapterService.isMock()).thenReturn(true);
269 
270         PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
271 
272         // Get the broadcast receiver to inject events
273         BroadcastReceiver injector = phPol.getBroadcastReceiver();
274 
275         // We send a connection successful for one profile since the re-connect *only* works if we
276         // have already connected successfully over one of the profiles
277         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
278         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
279         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
280         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
281         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
282         injector.onReceive(null /* context */, intent);
283 
284         // Return an empty list simulating that the above connection successful was nullified
285         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
286         when(mockHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
287 
288         // Also the A2DP should say that its not connected for same device
289         when(mockA2dpService.getConnectionState(device))
290                 .thenReturn(BluetoothProfile.STATE_DISCONNECTED);
291 
292         // To check that we have processed all the messages we need to have a hard sleep here. The
293         // reason being mockito can only verify synchronous calls, asynchronous calls are hidden
294         // from its mocking framework. Also, Looper does not provide a way to wait until all future
295         // messages are proceed.
296         try {
297             Thread.sleep(RETRY_TIMEOUT);
298         } catch (Exception ex) {
299         }
300 
301         // Check that we don't get any calls to reconnect
302         verify(mockA2dpService, never()).connect(eq(device));
303         verify(mockHeadsetService, never()).connect(eq(device));
304     }
305 
306     // Test that a device with no supported uuids is initialized properly and does not crash the
307     // stack
testNoSupportedUuids()308     public void testNoSupportedUuids() {
309         BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
310         BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
311 
312         // Create the mock objects required
313         AdapterService mockAdapterService = mock(AdapterService.class);
314         ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
315         HeadsetService mockHeadsetService = mock(HeadsetService.class);
316         A2dpService mockA2dpService = mock(A2dpService.class);
317 
318         // Mock the HeadsetService
319         when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
320         when(mockHeadsetService.getPriority(device))
321                 .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
322 
323         // Mock the A2DP service
324         when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
325         when(mockA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
326 
327         // Mock the looper
328         when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
329 
330         PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
331 
332         // Get the broadcast receiver to inject events.
333         BroadcastReceiver injector = phPol.getBroadcastReceiver();
334 
335         // Inject an event for UUIDs updated for a remote device with only HFP enabled
336         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
337         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
338 
339         // Put no UUIDs
340         injector.onReceive(null /* context */, intent);
341 
342         // To check that we have processed all the messages we need to have a hard sleep here. The
343         // reason being mockito can only verify synchronous calls, asynchronous calls are hidden
344         // from its mocking framework. Also, Looper does not provide a way to wait until all future
345         // messages are proceed.
346         try {
347             Thread.sleep(RETRY_TIMEOUT);
348         } catch (Exception ex) {
349         }
350 
351         // Check that we do not crash and not call any setPriority methods
352         verify(mockHeadsetService, never())
353                 .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
354         verify(mockA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
355     }
356 }
357