Loading android/app/src/com/android/bluetooth/btservice/PhonePolicy.java +45 −30 Original line number Diff line number Diff line Loading @@ -258,8 +258,8 @@ class PhonePolicy { int prevState) { debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", " + prevState + " -> " + nextState); if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)) && ( nextState == BluetoothProfile.STATE_CONNECTED)) { if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) { if (nextState == BluetoothProfile.STATE_CONNECTED) { switch (profileId) { case BluetoothProfile.A2DP: mA2dpRetrySet.remove(device); Loading @@ -269,7 +269,13 @@ class PhonePolicy { break; } connectOtherProfile(device); setProfileAutoConnectionPriority(device, profileId); setProfileAutoConnectionPriority(device, profileId, true); } if (prevState == BluetoothProfile.STATE_CONNECTING && nextState == BluetoothProfile.STATE_DISCONNECTED) { setProfileAutoConnectionPriority(device, profileId, false); } } } Loading Loading @@ -430,51 +436,60 @@ class PhonePolicy { } } private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId) { private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId, boolean autoConnect) { debugLog("setProfileAutoConnectionPriority: device=" + device + ", profile=" + profileId + ", autoConnect=" + autoConnect); switch (profileId) { case BluetoothProfile.HEADSET: case BluetoothProfile.HEADSET: { HeadsetService hsService = mFactory.getHeadsetService(); if ((hsService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != hsService.getPriority(device))) { List<BluetoothDevice> deviceList = hsService.getConnectedDevices(); adjustOtherHeadsetPriorities(hsService, deviceList); if (hsService == null) { warnLog("setProfileAutoConnectionPriority: HEADSET service is null"); break; } removeAutoConnectFromDisconnectedHeadsets(hsService); if (autoConnect) { hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); } break; case BluetoothProfile.A2DP: } case BluetoothProfile.A2DP: { A2dpService a2dpService = mFactory.getA2dpService(); if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpService.getPriority(device))) { List<BluetoothDevice> deviceList = a2dpService.getConnectedDevices(); adjustOtherSinkPriorities(a2dpService, deviceList); if (a2dpService == null) { warnLog("setProfileAutoConnectionPriority: A2DP service is null"); break; } removeAutoConnectFromDisconnectedA2dpSinks(a2dpService); if (autoConnect) { a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); } break; } default: Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId); break; } } private void adjustOtherHeadsetPriorities(HeadsetService hsService, List<BluetoothDevice> connectedDeviceList) { private void removeAutoConnectFromDisconnectedHeadsets(HeadsetService hsService) { List<BluetoothDevice> connectedDeviceList = hsService.getConnectedDevices(); for (BluetoothDevice device : mAdapterService.getBondedDevices()) { if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT && !connectedDeviceList.contains(device)) { debugLog("adjustOtherHeadsetPriorities, device " + device + " PRIORITY_ON"); debugLog("removeAutoConnectFromDisconnectedHeadsets, device " + device + " PRIORITY_ON"); hsService.setPriority(device, BluetoothProfile.PRIORITY_ON); } } } private void adjustOtherSinkPriorities(A2dpService a2dpService, List<BluetoothDevice> connectedDeviceList) { private void removeAutoConnectFromDisconnectedA2dpSinks(A2dpService a2dpService) { List<BluetoothDevice> connectedDeviceList = a2dpService.getConnectedDevices(); for (BluetoothDevice device : mAdapterService.getBondedDevices()) { if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT && !connectedDeviceList.contains(device)) { debugLog("adjustOtherSinkPriorities, device " + device + " PRIORITY_ON"); debugLog("removeAutoConnectFromDisconnectedA2dpSinks, device " + device + " PRIORITY_ON"); a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); } } Loading android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java +104 −1 Original line number Diff line number Diff line Loading @@ -82,7 +82,7 @@ public class PhonePolicyTest { doReturn(true).when(mAdapterService).isMock(); // Must be called to initialize services mAdapter = BluetoothAdapter.getDefaultAdapter(); mTestDevice = TestUtils.getTestDevice(mAdapter, 1); mTestDevice = TestUtils.getTestDevice(mAdapter, 99); PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS; mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory); } Loading Loading @@ -156,6 +156,109 @@ public class PhonePolicyTest { verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(mTestDevice)); } /** * Test that when an auto connect device is disconnected, its priority is set to ON if its * original priority is auto connect */ @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 bonded devices (just one) BluetoothDevice[] bondedDevices = new BluetoothDevice[4]; bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1); bondedDevices[2] = TestUtils.getTestDevice(mAdapter, 2); bondedDevices[3] = TestUtils.getTestDevice(mAdapter, 3); when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); doAnswer(invocation -> connectedDevices).when(mHeadsetService).getConnectedDevices(); // Make all devices auto connect when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); when(mHeadsetService.getPriority(bondedDevices[3])).thenReturn( BluetoothProfile.PRIORITY_OFF); // Make one of the device connected connectedDevices.add(bondedDevices[0]); when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( BluetoothProfile.STATE_CONNECTED); 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); // All other disconnected device's priority is set to ON, except disabled ones verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0], BluetoothProfile.PRIORITY_AUTO_CONNECT); verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1], BluetoothProfile.PRIORITY_ON); verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[2], BluetoothProfile.PRIORITY_ON); verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt()); when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( BluetoothProfile.PRIORITY_ON); when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn( BluetoothProfile.PRIORITY_ON); // Make another device connected connectedDevices.add(bondedDevices[1]); when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn( BluetoothProfile.STATE_CONNECTED); intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]); 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); // This device should be set to auto connect verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1], BluetoothProfile.PRIORITY_AUTO_CONNECT); verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt()); when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); // Make one of the device disconnect connectedDevices.remove(bondedDevices[0]); when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( BluetoothProfile.STATE_DISCONNECTED); intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); 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); // This should not have any effect verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).never()) .setPriority(bondedDevices[0], BluetoothProfile.PRIORITY_ON); intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING); intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); // This device should be set to ON verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0], BluetoothProfile.PRIORITY_ON); // Verify that we are not setting priorities to random devices and values verify(mHeadsetService, times(5)).setPriority(any(BluetoothDevice.class), anyInt()); } /** * Test that we will try to re-connect to a profile on a device if an attempt failed previously. * This is to add robustness to the connection mechanism Loading Loading
android/app/src/com/android/bluetooth/btservice/PhonePolicy.java +45 −30 Original line number Diff line number Diff line Loading @@ -258,8 +258,8 @@ class PhonePolicy { int prevState) { debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", " + prevState + " -> " + nextState); if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)) && ( nextState == BluetoothProfile.STATE_CONNECTED)) { if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) { if (nextState == BluetoothProfile.STATE_CONNECTED) { switch (profileId) { case BluetoothProfile.A2DP: mA2dpRetrySet.remove(device); Loading @@ -269,7 +269,13 @@ class PhonePolicy { break; } connectOtherProfile(device); setProfileAutoConnectionPriority(device, profileId); setProfileAutoConnectionPriority(device, profileId, true); } if (prevState == BluetoothProfile.STATE_CONNECTING && nextState == BluetoothProfile.STATE_DISCONNECTED) { setProfileAutoConnectionPriority(device, profileId, false); } } } Loading Loading @@ -430,51 +436,60 @@ class PhonePolicy { } } private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId) { private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId, boolean autoConnect) { debugLog("setProfileAutoConnectionPriority: device=" + device + ", profile=" + profileId + ", autoConnect=" + autoConnect); switch (profileId) { case BluetoothProfile.HEADSET: case BluetoothProfile.HEADSET: { HeadsetService hsService = mFactory.getHeadsetService(); if ((hsService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != hsService.getPriority(device))) { List<BluetoothDevice> deviceList = hsService.getConnectedDevices(); adjustOtherHeadsetPriorities(hsService, deviceList); if (hsService == null) { warnLog("setProfileAutoConnectionPriority: HEADSET service is null"); break; } removeAutoConnectFromDisconnectedHeadsets(hsService); if (autoConnect) { hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); } break; case BluetoothProfile.A2DP: } case BluetoothProfile.A2DP: { A2dpService a2dpService = mFactory.getA2dpService(); if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpService.getPriority(device))) { List<BluetoothDevice> deviceList = a2dpService.getConnectedDevices(); adjustOtherSinkPriorities(a2dpService, deviceList); if (a2dpService == null) { warnLog("setProfileAutoConnectionPriority: A2DP service is null"); break; } removeAutoConnectFromDisconnectedA2dpSinks(a2dpService); if (autoConnect) { a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); } break; } default: Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId); break; } } private void adjustOtherHeadsetPriorities(HeadsetService hsService, List<BluetoothDevice> connectedDeviceList) { private void removeAutoConnectFromDisconnectedHeadsets(HeadsetService hsService) { List<BluetoothDevice> connectedDeviceList = hsService.getConnectedDevices(); for (BluetoothDevice device : mAdapterService.getBondedDevices()) { if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT && !connectedDeviceList.contains(device)) { debugLog("adjustOtherHeadsetPriorities, device " + device + " PRIORITY_ON"); debugLog("removeAutoConnectFromDisconnectedHeadsets, device " + device + " PRIORITY_ON"); hsService.setPriority(device, BluetoothProfile.PRIORITY_ON); } } } private void adjustOtherSinkPriorities(A2dpService a2dpService, List<BluetoothDevice> connectedDeviceList) { private void removeAutoConnectFromDisconnectedA2dpSinks(A2dpService a2dpService) { List<BluetoothDevice> connectedDeviceList = a2dpService.getConnectedDevices(); for (BluetoothDevice device : mAdapterService.getBondedDevices()) { if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT && !connectedDeviceList.contains(device)) { debugLog("adjustOtherSinkPriorities, device " + device + " PRIORITY_ON"); debugLog("removeAutoConnectFromDisconnectedA2dpSinks, device " + device + " PRIORITY_ON"); a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); } } Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java +104 −1 Original line number Diff line number Diff line Loading @@ -82,7 +82,7 @@ public class PhonePolicyTest { doReturn(true).when(mAdapterService).isMock(); // Must be called to initialize services mAdapter = BluetoothAdapter.getDefaultAdapter(); mTestDevice = TestUtils.getTestDevice(mAdapter, 1); mTestDevice = TestUtils.getTestDevice(mAdapter, 99); PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS; mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory); } Loading Loading @@ -156,6 +156,109 @@ public class PhonePolicyTest { verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(mTestDevice)); } /** * Test that when an auto connect device is disconnected, its priority is set to ON if its * original priority is auto connect */ @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 bonded devices (just one) BluetoothDevice[] bondedDevices = new BluetoothDevice[4]; bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1); bondedDevices[2] = TestUtils.getTestDevice(mAdapter, 2); bondedDevices[3] = TestUtils.getTestDevice(mAdapter, 3); when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); doAnswer(invocation -> connectedDevices).when(mHeadsetService).getConnectedDevices(); // Make all devices auto connect when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); when(mHeadsetService.getPriority(bondedDevices[3])).thenReturn( BluetoothProfile.PRIORITY_OFF); // Make one of the device connected connectedDevices.add(bondedDevices[0]); when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( BluetoothProfile.STATE_CONNECTED); 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); // All other disconnected device's priority is set to ON, except disabled ones verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0], BluetoothProfile.PRIORITY_AUTO_CONNECT); verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1], BluetoothProfile.PRIORITY_ON); verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[2], BluetoothProfile.PRIORITY_ON); verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt()); when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( BluetoothProfile.PRIORITY_ON); when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn( BluetoothProfile.PRIORITY_ON); // Make another device connected connectedDevices.add(bondedDevices[1]); when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn( BluetoothProfile.STATE_CONNECTED); intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]); 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); // This device should be set to auto connect verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1], BluetoothProfile.PRIORITY_AUTO_CONNECT); verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt()); when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( BluetoothProfile.PRIORITY_AUTO_CONNECT); // Make one of the device disconnect connectedDevices.remove(bondedDevices[0]); when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( BluetoothProfile.STATE_DISCONNECTED); intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); 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); // This should not have any effect verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).never()) .setPriority(bondedDevices[0], BluetoothProfile.PRIORITY_ON); intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING); intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); // This device should be set to ON verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0], BluetoothProfile.PRIORITY_ON); // Verify that we are not setting priorities to random devices and values verify(mHeadsetService, times(5)).setPriority(any(BluetoothDevice.class), anyInt()); } /** * Test that we will try to re-connect to a profile on a device if an attempt failed previously. * This is to add robustness to the connection mechanism Loading