| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.btservice; |
| |
| import static com.android.bluetooth.TestUtils.getTestDevice; |
| import static com.android.bluetooth.TestUtils.waitForLooperToFinishScheduledTask; |
| |
| import static org.mockito.Mockito.*; |
| |
| import android.bluetooth.BluetoothA2dp; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHeadset; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothUuid; |
| import android.content.Intent; |
| import android.os.HandlerThread; |
| import android.os.ParcelUuid; |
| |
| import androidx.test.filters.MediumTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.bluetooth.TestUtils; |
| import com.android.bluetooth.Utils; |
| import com.android.bluetooth.a2dp.A2dpService; |
| import com.android.bluetooth.btservice.storage.DatabaseManager; |
| import com.android.bluetooth.hfp.HeadsetService; |
| import com.android.bluetooth.le_audio.LeAudioService; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class PhonePolicyTest { |
| private static final int MAX_CONNECTED_AUDIO_DEVICES = 5; |
| private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; |
| private static final int CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS = 1000; |
| private static final int CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS = |
| CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS * 3; |
| |
| private HandlerThread mHandlerThread; |
| private BluetoothAdapter mAdapter; |
| private PhonePolicy mPhonePolicy; |
| private boolean mOriginalDualModeState; |
| |
| @Mock private AdapterService mAdapterService; |
| @Mock private ServiceFactory mServiceFactory; |
| @Mock private HeadsetService mHeadsetService; |
| @Mock private A2dpService mA2dpService; |
| @Mock private LeAudioService mLeAudioService; |
| |
| @Mock private DatabaseManager mDatabaseManager; |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| // Stub A2DP and HFP |
| when(mHeadsetService.connect(any(BluetoothDevice.class))).thenReturn(true); |
| when(mA2dpService.connect(any(BluetoothDevice.class))).thenReturn(true); |
| // Prepare the TestUtils |
| TestUtils.setAdapterService(mAdapterService); |
| // Configure the maximum connected audio devices |
| doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices(); |
| doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); |
| // Setup the mocked factory to return mocked services |
| doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService(); |
| doReturn(mA2dpService).when(mServiceFactory).getA2dpService(); |
| doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService(); |
| // Start handler thread for this test |
| mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread"); |
| mHandlerThread.start(); |
| // Mock the looper |
| when(mAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper()); |
| // Tell the AdapterService that it is a mock (see isMock documentation) |
| doReturn(true).when(mAdapterService).isMock(); |
| // Must be called to initialize services |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS; |
| mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory); |
| mOriginalDualModeState = Utils.isDualModeAudioEnabled(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (mHandlerThread != null) { |
| mHandlerThread.quitSafely(); |
| } |
| TestUtils.clearAdapterService(mAdapterService); |
| Utils.setDualModeAudioStateForTesting(mOriginalDualModeState); |
| } |
| |
| /** |
| * Test that when new UUIDs are refreshed for a device then we set the priorities for various |
| * profiles accurately. The following profiles should have ON priorities: |
| * A2DP, HFP, HID and PAN |
| */ |
| @Test |
| public void testProcessInitProfilePriorities() { |
| mPhonePolicy.mAutoConnectProfilesSupported = false; |
| |
| BluetoothDevice device = getTestDevice(mAdapter, 0); |
| // Mock the HeadsetService to return unknown connection policy |
| when(mHeadsetService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| // Mock the A2DP service to return undefined unknown connection policy |
| when(mA2dpService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); |
| |
| // Inject an event for UUIDs updated for a remote device with only HFP enabled |
| ParcelUuid[] uuids = new ParcelUuid[2]; |
| uuids[0] = BluetoothUuid.HFP; |
| uuids[1] = BluetoothUuid.A2DP_SINK; |
| mPhonePolicy.onUuidsDiscovered(device, uuids); |
| |
| // Check that the priorities of the devices for preferred profiles are set to ON |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| |
| |
| @Test |
| public void testProcessInitProfilePriorities_WithAutoConnect() { |
| mPhonePolicy.mAutoConnectProfilesSupported = true; |
| |
| BluetoothDevice device = getTestDevice(mAdapter, 0); |
| // Mock the HeadsetService to return unknown connection policy |
| when(mHeadsetService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| // Mock the A2DP service to return undefined unknown connection policy |
| when(mA2dpService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); |
| |
| // Inject an event for UUIDs updated for a remote device with only HFP enabled |
| ParcelUuid[] uuids = new ParcelUuid[2]; |
| uuids[0] = BluetoothUuid.HFP; |
| uuids[1] = BluetoothUuid.A2DP_SINK; |
| mPhonePolicy.onUuidsDiscovered(device, uuids); |
| |
| // Check auto connect |
| verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| |
| @Test |
| public void testProcessInitProfilePriorities_LeAudioDisabledByDefault() { |
| BluetoothDevice device = getTestDevice(mAdapter, 0); |
| when(mAdapterService.isLeAudioAllowed(device)).thenReturn(true); |
| |
| // Auto connect to LE audio, HFP, A2DP |
| processInitProfilePriorities_LeAudioHelper(true, true, false); |
| verify(mLeAudioService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| // Does not auto connect and allow HFP and A2DP to be connected |
| processInitProfilePriorities_LeAudioHelper(true, false, false); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| // Auto connect to LE audio but disallow HFP and A2DP |
| processInitProfilePriorities_LeAudioHelper(false, true, false); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| // Does not auto connect and disallow HFP and A2DP to be connected |
| processInitProfilePriorities_LeAudioHelper(false, false, false); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| |
| @Test |
| public void testProcessInitProfilePriorities_LeAudioEnabledByDefault() { |
| BluetoothDevice device = getTestDevice(mAdapter, 0); |
| when(mAdapterService.isLeAudioAllowed(device)).thenReturn(true); |
| |
| // Auto connect to LE audio, HFP, A2DP |
| processInitProfilePriorities_LeAudioHelper(true, true, true); |
| verify(mLeAudioService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| // Does not auto connect and allow HFP and A2DP to be connected |
| processInitProfilePriorities_LeAudioHelper(true, false, true); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| // Auto connect to LE audio but disallow HFP and A2DP |
| processInitProfilePriorities_LeAudioHelper(false, true, true); |
| verify(mLeAudioService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| |
| // Does not auto connect and disallow HFP and A2DP to be connected |
| processInitProfilePriorities_LeAudioHelper(false, false, true); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) |
| .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| } |
| |
| private void processInitProfilePriorities_LeAudioHelper( |
| boolean dualModeEnabled, boolean autoConnect, boolean leAudioEnabledByDefault) { |
| Utils.setDualModeAudioStateForTesting(dualModeEnabled); |
| mPhonePolicy.setLeAudioEnabledByDefaultForTesting(leAudioEnabledByDefault); |
| mPhonePolicy.mAutoConnectProfilesSupported = autoConnect; |
| |
| BluetoothDevice device = getTestDevice(mAdapter, 0); |
| // Mock the HFP, A2DP and LE audio services to return unknown connection policy |
| when(mHeadsetService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| when(mA2dpService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| when(mLeAudioService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); |
| |
| // Inject an event for UUIDs updated for a remote device with only HFP enabled |
| ParcelUuid[] uuids = new ParcelUuid[3]; |
| uuids[0] = BluetoothUuid.HFP; |
| uuids[1] = BluetoothUuid.A2DP_SINK; |
| uuids[2] = BluetoothUuid.LE_AUDIO; |
| mPhonePolicy.onUuidsDiscovered(device, uuids); |
| } |
| |
| /** |
| * Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and |
| * A2DP enabled. NOTE that the assumption is that we have already done the pairing previously |
| * and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as |
| * part of post pairing process). |
| */ |
| @Test |
| public void testAdapterOnAutoConnect() { |
| // Return desired values from the mocked object(s) |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| when(mAdapterService.isQuietModeEnabled()).thenReturn(false); |
| |
| // Return a list of connection order |
| BluetoothDevice bondedDevice = getTestDevice(mAdapter, 0); |
| when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(bondedDevice); |
| when(mAdapterService.getBondState(bondedDevice)).thenReturn(BluetoothDevice.BOND_BONDED); |
| |
| // Return CONNECTION_POLICY_ALLOWED over HFP and A2DP |
| when(mHeadsetService.getConnectionPolicy(bondedDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| // Inject an event that the adapter is turned on. |
| Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); |
| intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| |
| // Check that we got a request to connect over HFP and A2DP |
| verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevice)); |
| verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevice)); |
| } |
| |
| /** |
| * Test that when an active device is disconnected, we will not auto connect it |
| */ |
| @Test |
| public void testDisconnectNoAutoConnect() { |
| // Return desired values from the mocked object(s) |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| when(mAdapterService.isQuietModeEnabled()).thenReturn(false); |
| |
| // Return a list of connection order |
| List<BluetoothDevice> connectionOrder = new ArrayList<>(); |
| connectionOrder.add(getTestDevice(mAdapter, 0)); |
| connectionOrder.add(getTestDevice(mAdapter, 1)); |
| connectionOrder.add(getTestDevice(mAdapter, 2)); |
| connectionOrder.add(getTestDevice(mAdapter, 3)); |
| |
| when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn( |
| connectionOrder.get(0)); |
| |
| // Make all devices auto connect |
| when(mHeadsetService.getConnectionPolicy(connectionOrder.get(0))).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mHeadsetService.getConnectionPolicy(connectionOrder.get(1))).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mHeadsetService.getConnectionPolicy(connectionOrder.get(2))).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mHeadsetService.getConnectionPolicy(connectionOrder.get(3))).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| |
| // Make one of the device active |
| Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(0)); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Only calls setConnection on device connectionOrder.get(0) with STATE_CONNECTED |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnection( |
| connectionOrder.get(0), true); |
| verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(1)), anyBoolean()); |
| verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(2)), anyBoolean()); |
| verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(3)), anyBoolean()); |
| |
| // Make another device active |
| when(mHeadsetService.getConnectionState(connectionOrder.get(1))).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(1)); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Only calls setConnection on device connectionOrder.get(1) with STATE_CONNECTED |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection( |
| connectionOrder.get(0), true); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection( |
| connectionOrder.get(1), true); |
| verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(2)), anyBoolean()); |
| verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(3)), anyBoolean()); |
| |
| // Disconnect a2dp for the device from previous STATE_CONNECTED |
| when(mHeadsetService.getConnectionState(connectionOrder.get(1))).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(1)); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Verify that we do not call setConnection, nor setDisconnection on disconnect |
| // from previous STATE_CONNECTED |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection( |
| connectionOrder.get(1), true); |
| verify(mDatabaseManager, never()).setDisconnection(connectionOrder.get(1)); |
| |
| // Disconnect a2dp for the device from previous STATE_DISCONNECTING |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, |
| BluetoothProfile.STATE_DISCONNECTING); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Verify that we do not call setConnection, but instead setDisconnection on disconnect |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection( |
| connectionOrder.get(1), true); |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection( |
| connectionOrder.get(1)); |
| |
| // Make the current active device fail to connect |
| when(mA2dpService.getConnectionState(connectionOrder.get(1))).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| updateProfileConnectionStateHelper(connectionOrder.get(1), BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Verify we don't call deleteConnection as that only happens when we disconnect a2dp |
| verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection( |
| connectionOrder.get(1)); |
| |
| // Verify we didn't have any unexpected calls to setConnection or deleteConnection |
| verify(mDatabaseManager, times(2)).setConnection(any(BluetoothDevice.class), anyBoolean()); |
| verify(mDatabaseManager, times(1)).setDisconnection(any(BluetoothDevice.class)); |
| } |
| |
| /** |
| * Test that we will try to re-connect to a profile on a device if other profile(s) are |
| * connected. This is to add robustness to the connection mechanism |
| */ |
| @Test |
| public void testReconnectOnPartialConnect() { |
| // Return a list of bonded devices (just one) |
| BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; |
| bondedDevices[0] = getTestDevice(mAdapter, 0); |
| when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are |
| // auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| |
| // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP |
| // To enable that we need to make sure that HeadsetService returns the device as list of |
| // connected devices |
| ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); |
| hsConnectedDevices.add(bondedDevices[0]); |
| when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); |
| // Also the A2DP should say that its not connected for same device |
| when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| // ACL is connected, lets simulate this. |
| when(mAdapterService.getConnectionState(bondedDevices[0])) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we get a call to A2DP connect |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| eq(bondedDevices[0])); |
| } |
| |
| /** |
| * Test that connectOtherProfile will not trigger any actions when ACL is disconnected. This is |
| * to add robustness to the connection mechanism |
| */ |
| @Test |
| public void testConnectOtherProfileWhileDeviceIsDisconnected() { |
| // Return a list of bonded devices (just one) |
| BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; |
| bondedDevices[0] = getTestDevice(mAdapter, 0); |
| when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are |
| // auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[0])) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[0])) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| |
| // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP |
| // To enable that we need to make sure that HeadsetService returns the device as list of |
| // connected devices |
| ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); |
| hsConnectedDevices.add(bondedDevices[0]); |
| when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); |
| // Also the A2DP should say that it's not connected for same device |
| when(mA2dpService.getConnectionState(bondedDevices[0])) |
| .thenReturn(BluetoothProfile.STATE_DISCONNECTED); |
| |
| // ACL is disconnected just after HEADSET profile got connected and connectOtherProfile |
| // was scheduled. Lets simulate this. |
| when(mAdapterService.getConnectionState(bondedDevices[0])) |
| .thenReturn(BluetoothProfile.STATE_DISCONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| updateProfileConnectionStateHelper( |
| bondedDevices[0], |
| BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that there will be no A2DP connect |
| verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()) |
| .connect(eq(bondedDevices[0])); |
| } |
| |
| /** |
| * Test that we will try to re-connect to a profile on a device next time if a previous attempt |
| * failed partially. This will make sure the connection mechanism still works at next try while |
| * the previous attempt is some profiles connected on a device but some not. |
| */ |
| @Test |
| public void testReconnectOnPartialConnect_PreviousPartialFail() { |
| List<BluetoothDevice> connectionOrder = new ArrayList<>(); |
| BluetoothDevice testDevice = getTestDevice(mAdapter, 0); |
| connectionOrder.add(testDevice); |
| |
| // ACL is connected, lets simulate this. |
| when(mAdapterService.getConnectionState(testDevice)) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| |
| when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn( |
| connectionOrder.get(0)); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are |
| // auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(connectionOrder.get(0))).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(connectionOrder.get(0))).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| |
| // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP |
| // To enable that we need to make sure that HeadsetService returns the device among a list |
| // of connected devices |
| ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); |
| hsConnectedDevices.add(connectionOrder.get(0)); |
| when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); |
| // Also the A2DP should say that its not connected for same device |
| when(mA2dpService.getConnectionState(connectionOrder.get(0))).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| // We send a connection success event for one profile since the re-connect *only* works if |
| // we have already connected successfully over one of the profiles |
| updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we get a call to A2DP reconnect |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| connectionOrder.get(0)); |
| |
| // We send a connection failure event for the attempted profile, and keep the connected |
| // profile connected. |
| updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.A2DP, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Verify no one changes the priority of the failed profile |
| verify(mA2dpService, never()).setConnectionPolicy(eq(connectionOrder.get(0)), anyInt()); |
| |
| // Send a connection success event for one profile again without disconnecting all profiles |
| updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we won't get a call to A2DP reconnect again before all profiles disconnected |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| connectionOrder.get(0)); |
| |
| // Send a disconnection event for all connected profiles |
| hsConnectedDevices.remove(connectionOrder.get(0)); |
| updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); |
| |
| waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); |
| |
| // Send a connection success event for one profile again to trigger re-connect |
| hsConnectedDevices.add(connectionOrder.get(0)); |
| updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we get a call to A2DP connect again |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect( |
| connectionOrder.get(0)); |
| } |
| |
| /** |
| * Test that a second device will auto-connect if there is already one connected device. |
| * |
| * Even though we currently only set one device to be auto connect. The consumer of the auto |
| * connect property works independently so that we will connect to all devices that are in |
| * auto connect mode. |
| */ |
| @Test |
| public void testAutoConnectMultipleDevices() { |
| final int kMaxTestDevices = 3; |
| BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices]; |
| ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); |
| ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>(); |
| BluetoothDevice a2dpNotConnectedDevice1 = null; |
| BluetoothDevice a2dpNotConnectedDevice2 = null; |
| |
| for (int i = 0; i < kMaxTestDevices; i++) { |
| BluetoothDevice testDevice = getTestDevice(mAdapter, i); |
| testDevices[i] = testDevice; |
| |
| // ACL is connected, lets simulate this. |
| when(mAdapterService.getConnectionState(testDevice)) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles |
| // are auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP |
| // To enable that we need to make sure that HeadsetService returns the device as list |
| // of connected devices. |
| hsConnectedDevices.add(testDevice); |
| // Connect A2DP for all devices except the last one |
| if (i < (kMaxTestDevices - 2)) { |
| a2dpConnectedDevices.add(testDevice); |
| } |
| } |
| a2dpNotConnectedDevice1 = hsConnectedDevices.get(kMaxTestDevices - 1); |
| a2dpNotConnectedDevice2 = hsConnectedDevices.get(kMaxTestDevices - 2); |
| |
| when(mAdapterService.getBondedDevices()).thenReturn(testDevices); |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); |
| when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices); |
| // Two of the A2DP devices are not connected |
| when(mA2dpService.getConnectionState(a2dpNotConnectedDevice1)).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mA2dpService.getConnectionState(a2dpNotConnectedDevice2)).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| updateProfileConnectionStateHelper(a2dpNotConnectedDevice1, BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| updateProfileConnectionStateHelper(a2dpNotConnectedDevice2, BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we get a call to A2DP connect |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| eq(a2dpNotConnectedDevice1)); |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| eq(a2dpNotConnectedDevice2)); |
| } |
| |
| /** |
| * Test that the connection policy of all devices are set as appropriate if there is one |
| * connected device. |
| * - The HFP and A2DP connect priority for connected devices is set to |
| * BluetoothProfile.PRIORITY_AUTO_CONNECT |
| * - The HFP and A2DP connect priority for bonded devices is set to |
| * BluetoothProfile.CONNECTION_POLICY_ALLOWED |
| */ |
| @Test |
| public void testSetConnectionPolicyMultipleDevices() { |
| // testDevices[0] - connected for both HFP and A2DP |
| // testDevices[1] - connected only for HFP - will auto-connect for A2DP |
| // testDevices[2] - connected only for A2DP - will auto-connect for HFP |
| // testDevices[3] - not connected |
| final int kMaxTestDevices = 4; |
| BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices]; |
| ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); |
| ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>(); |
| |
| for (int i = 0; i < kMaxTestDevices; i++) { |
| BluetoothDevice testDevice = getTestDevice(mAdapter, i); |
| testDevices[i] = testDevice; |
| |
| // ACL is connected, lets simulate this. |
| when(mAdapterService.getConnectionState(testDevices[i])) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| |
| // Connect HFP and A2DP for each device as appropriate. |
| // Return PRIORITY_AUTO_CONNECT only for testDevices[0] |
| if (i == 0) { |
| hsConnectedDevices.add(testDevice); |
| a2dpConnectedDevices.add(testDevice); |
| when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| if (i == 1) { |
| hsConnectedDevices.add(testDevice); |
| when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(testDevice)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| if (i == 2) { |
| a2dpConnectedDevices.add(testDevice); |
| when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(testDevice)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| if (i == 3) { |
| // Device not connected |
| when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(testDevice)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| } |
| } |
| when(mAdapterService.getBondedDevices()).thenReturn(testDevices); |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); |
| when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices); |
| // Some of the devices are not connected |
| // testDevices[0] - connected for both HFP and A2DP |
| when(mHeadsetService.getConnectionState(testDevices[0])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mA2dpService.getConnectionState(testDevices[0])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| // testDevices[1] - connected only for HFP - will auto-connect for A2DP |
| when(mHeadsetService.getConnectionState(testDevices[1])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mA2dpService.getConnectionState(testDevices[1])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| // testDevices[2] - connected only for A2DP - will auto-connect for HFP |
| when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mA2dpService.getConnectionState(testDevices[2])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| // testDevices[3] - not connected |
| when(mHeadsetService.getConnectionState(testDevices[3])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mA2dpService.getConnectionState(testDevices[3])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| |
| // Generate connection state changed for HFP for testDevices[1] and trigger |
| // auto-connect for A2DP. |
| updateProfileConnectionStateHelper(testDevices[1], BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we get a call to A2DP connect |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| eq(testDevices[1])); |
| |
| // testDevices[1] auto-connect completed for A2DP |
| a2dpConnectedDevices.add(testDevices[1]); |
| when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices); |
| when(mA2dpService.getConnectionState(testDevices[1])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| |
| // Check the connect priorities for all devices |
| // - testDevices[0] - connected for HFP and A2DP: setConnectionPolicy() should not be called |
| // - testDevices[1] - connection state changed for HFP should no longer trigger auto |
| // connect priority change since it is now triggered by A2DP active |
| // device change intent |
| // - testDevices[2] - connected for A2DP: setConnectionPolicy() should not be called |
| // - testDevices[3] - not connected for HFP nor A2DP: setConnectionPolicy() should not be |
| // called |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt()); |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[1]), |
| eq(BluetoothProfile.PRIORITY_AUTO_CONNECT)); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt()); |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt()); |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt()); |
| clearInvocations(mHeadsetService, mA2dpService); |
| |
| // Generate connection state changed for A2DP for testDevices[2] and trigger |
| // auto-connect for HFP. |
| updateProfileConnectionStateHelper(testDevices[2], BluetoothProfile.A2DP, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we get a call to HFP connect |
| verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( |
| eq(testDevices[2])); |
| |
| // testDevices[2] auto-connect completed for HFP |
| hsConnectedDevices.add(testDevices[2]); |
| when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); |
| when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| |
| // Check the connect priorities for all devices |
| // - testDevices[0] - connected for HFP and A2DP: setConnectionPolicy() should not be called |
| // - testDevices[1] - connected for HFP and A2DP: setConnectionPolicy() should not be called |
| // - testDevices[2] - connection state changed for A2DP should no longer trigger auto |
| // connect priority change since it is now triggered by A2DP |
| // active device change intent |
| // - testDevices[3] - not connected for HFP nor A2DP: setConnectionPolicy() should not be |
| // called |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt()); |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt()); |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[2]), |
| eq(BluetoothProfile.PRIORITY_AUTO_CONNECT)); |
| verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt()); |
| verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt()); |
| clearInvocations(mHeadsetService, mA2dpService); |
| } |
| |
| /** |
| * Test that we will not try to reconnect on a profile if all the connections failed |
| */ |
| @Test |
| public void testNoReconnectOnNoConnect() { |
| // Return a list of bonded devices (just one) |
| BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; |
| bondedDevices[0] = getTestDevice(mAdapter, 0); |
| when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are |
| // auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| |
| // Return an empty list simulating that the above connection successful was nullified |
| when(mHeadsetService.getConnectedDevices()).thenReturn(Collections.emptyList()); |
| when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList()); |
| |
| // Both A2DP and HFP should say this device is not connected, except for the intent |
| when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| |
| // Check that we don't get any calls to reconnect |
| verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect( |
| eq(bondedDevices[0])); |
| verify(mHeadsetService, never()).connect(eq(bondedDevices[0])); |
| } |
| |
| /** |
| * Test that we will not try to reconnect on a profile if all the connections failed |
| * with multiple devices |
| */ |
| @Test |
| public void testNoReconnectOnNoConnect_MultiDevice() { |
| // Return a list of bonded devices (just one) |
| BluetoothDevice[] bondedDevices = new BluetoothDevice[2]; |
| bondedDevices[0] = getTestDevice(mAdapter, 0); |
| bondedDevices[1] = getTestDevice(mAdapter, 1); |
| when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are |
| // auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[1])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[1])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| |
| // Return an a list with only the second device as connected |
| when(mHeadsetService.getConnectedDevices()).thenReturn( |
| Collections.singletonList(bondedDevices[1])); |
| when(mA2dpService.getConnectedDevices()).thenReturn( |
| Collections.singletonList(bondedDevices[1])); |
| |
| // Both A2DP and HFP should say this device is not connected, except for the intent |
| when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| |
| // ACL is connected for both devices. |
| when(mAdapterService.getConnectionState(bondedDevices[0])) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| when(mAdapterService.getConnectionState(bondedDevices[1])) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| |
| // Check that we don't get any calls to reconnect |
| verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect( |
| eq(bondedDevices[0])); |
| verify(mHeadsetService, never()).connect(eq(bondedDevices[0])); |
| } |
| |
| /** |
| * Test that we will try to connect to other profiles of a device if it is partially connected |
| */ |
| @Test |
| public void testReconnectOnPartialConnect_MultiDevice() { |
| // Return a list of bonded devices (just one) |
| BluetoothDevice[] bondedDevices = new BluetoothDevice[2]; |
| bondedDevices[0] = getTestDevice(mAdapter, 0); |
| bondedDevices[1] = getTestDevice(mAdapter, 1); |
| when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); |
| |
| // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are |
| // auto-connectable. |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mHeadsetService.getConnectionPolicy(bondedDevices[1])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mA2dpService.getConnectionPolicy(bondedDevices[1])).thenReturn( |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| |
| when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); |
| |
| // Return an a list with only the second device as connected |
| when(mHeadsetService.getConnectedDevices()).thenReturn( |
| Collections.singletonList(bondedDevices[1])); |
| when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList()); |
| |
| // Both A2DP and HFP should say this device is not connected, except for the intent |
| when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| |
| // ACL is connected, lets simulate this. |
| when(mAdapterService.getConnectionState(bondedDevices[1])) |
| .thenReturn(BluetoothProfile.STATE_CONNECTED); |
| |
| // We send a connection successful for one profile since the re-connect *only* works if we |
| // have already connected successfully over one of the profiles |
| updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| |
| // Check that we do get A2DP call to reconnect, because HEADSET just got connected |
| verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)) |
| .connect(eq(bondedDevices[1])); |
| } |
| |
| /** |
| * Test that a device with no supported uuids is initialized properly and does not crash the |
| * stack |
| */ |
| @Test |
| public void testNoSupportedUuids() { |
| // Mock the HeadsetService to return undefined priority |
| BluetoothDevice device = getTestDevice(mAdapter, 0); |
| when(mHeadsetService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| // Mock the A2DP service to return undefined priority |
| when(mA2dpService.getConnectionPolicy(device)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| // Inject an event for UUIDs updated for a remote device with no supported services |
| mPhonePolicy.onUuidsDiscovered(device, null); |
| |
| // Check that we do not crash and not call any setPriority methods |
| verify(mHeadsetService, |
| after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()) |
| .setConnectionPolicy(eq(device), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED)); |
| verify(mA2dpService, never()) |
| .setConnectionPolicy(eq(device), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED)); |
| } |
| |
| private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId, |
| int nextState, int prevState) { |
| Intent intent; |
| switch (profileId) { |
| case BluetoothProfile.A2DP: |
| when(mA2dpService.getConnectionState(device)).thenReturn(nextState); |
| intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); |
| break; |
| case BluetoothProfile.HEADSET: |
| when(mHeadsetService.getConnectionState(device)).thenReturn(nextState); |
| intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); |
| break; |
| default: |
| intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); |
| break; |
| } |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, nextState); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); |
| } |
| } |