Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f96ebc6b authored by Cheney Ni's avatar Cheney Ni Committed by android-build-merger
Browse files

Merge "PhonePolicy: Fix no auto-connect to other profiles while previous...

Merge "PhonePolicy: Fix no auto-connect to other profiles while previous attempt failed partially" am: 04c08349 am: 1226cd33
am: 57c46951

Change-Id: I211fe739ebade2595a3a828efb1598bddf76bc2a
parents ce211ca9 57c46951
Loading
Loading
Loading
Loading
+62 −49
Original line number Diff line number Diff line
@@ -284,10 +284,12 @@ class PhonePolicy {
                }
                connectOtherProfile(device);
            }
            if (prevState == BluetoothProfile.STATE_CONNECTING
                    && nextState == BluetoothProfile.STATE_DISCONNECTED) {
            if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
                handleAllProfilesDisconnected(device);
                if (prevState == BluetoothProfile.STATE_CONNECTING) {
                    HeadsetService hsService = mFactory.getHeadsetService();
                boolean hsDisconnected = hsService == null || hsService.getConnectionState(device)
                    boolean hsDisconnected = hsService == null
                            || hsService.getConnectionState(device)
                            == BluetoothProfile.STATE_DISCONNECTED;
                    A2dpService a2dpService = mFactory.getA2dpService();
                    boolean a2dpDisconnected = a2dpService == null
@@ -302,6 +304,7 @@ class PhonePolicy {
                }
            }
        }
    }

    private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
        debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
@@ -326,6 +329,45 @@ class PhonePolicy {
        }
    }

    private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
        boolean atLeastOneProfileConnectedForDevice = false;
        boolean allProfilesEmpty = true;
        HeadsetService hsService = mFactory.getHeadsetService();
        A2dpService a2dpService = mFactory.getA2dpService();
        PanService panService = mFactory.getPanService();

        if (hsService != null) {
            List<BluetoothDevice> hsConnDevList = hsService.getConnectedDevices();
            allProfilesEmpty &= hsConnDevList.isEmpty();
            atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
        }
        if (a2dpService != null) {
            List<BluetoothDevice> a2dpConnDevList = a2dpService.getConnectedDevices();
            allProfilesEmpty &= a2dpConnDevList.isEmpty();
            atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
        }
        if (panService != null) {
            List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
            allProfilesEmpty &= panConnDevList.isEmpty();
            atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
        }

        if (!atLeastOneProfileConnectedForDevice) {
            // Consider this device as fully disconnected, don't bother connecting others
            debugLog("handleAllProfilesDisconnected: all profiles disconnected for " + device);
            mHeadsetRetrySet.remove(device);
            mA2dpRetrySet.remove(device);
            if (allProfilesEmpty) {
                debugLog("handleAllProfilesDisconnected: all profiles disconnected for all"
                        + " devices");
                // reset retry status so that in the next round we can start retrying connections
                resetStates();
            }
            return true;
        }
        return false;
    }

    private void resetStates() {
        mHeadsetRetrySet.clear();
        mA2dpRetrySet.clear();
@@ -410,45 +452,15 @@ class PhonePolicy {
            warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState());
            return;
        }
        if (handleAllProfilesDisconnected(device)) {
            debugLog("processConnectOtherProfiles: all profiles disconnected for " + device);
            return;
        }

        HeadsetService hsService = mFactory.getHeadsetService();
        A2dpService a2dpService = mFactory.getA2dpService();
        PanService panService = mFactory.getPanService();

        boolean atLeastOneProfileConnectedForDevice = false;
        boolean allProfilesEmpty = true;
        List<BluetoothDevice> a2dpConnDevList = null;
        List<BluetoothDevice> hsConnDevList = null;
        List<BluetoothDevice> panConnDevList = null;

        if (hsService != null) {
            hsConnDevList = hsService.getConnectedDevices();
            allProfilesEmpty &= hsConnDevList.isEmpty();
            atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
        }
        if (a2dpService != null) {
            a2dpConnDevList = a2dpService.getConnectedDevices();
            allProfilesEmpty &= a2dpConnDevList.isEmpty();
            atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
        }
        if (panService != null) {
            panConnDevList = panService.getConnectedDevices();
            allProfilesEmpty &= panConnDevList.isEmpty();
            atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
        }

        if (!atLeastOneProfileConnectedForDevice) {
            // Consider this device as fully disconnected, don't bother connecting others
            debugLog("processConnectOtherProfiles, all profiles disconnected for " + device);
            mHeadsetRetrySet.remove(device);
            mA2dpRetrySet.remove(device);
            if (allProfilesEmpty) {
                debugLog("processConnectOtherProfiles, all profiles disconnected for all devices");
                // reset retry status so that in the next round we can start retrying connections
                resetStates();
            }
            return;
        }

        if (hsService != null) {
            if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
                    >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
@@ -468,6 +480,7 @@ class PhonePolicy {
            }
        }
        if (panService != null) {
            List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
            // TODO: the panConnDevList.isEmpty() check below should be removed once
            // Multi-PAN is supported.
            if (panConnDevList.isEmpty() && (panService.getPriority(device)
+122 −52
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.ParcelUuid;
@@ -235,21 +234,20 @@ public class PhonePolicyTest {
        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);

        // This should not have any effect
        // Verify that the priority of previous active device won't be changed while active device
        // set to null
        verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
                bondedDevices[1], BluetoothProfile.PRIORITY_ON);
        verify(mHeadsetService).setPriority(bondedDevices[1],
                BluetoothProfile.PRIORITY_AUTO_CONNECT);
        verify(mHeadsetService, never()).setPriority(bondedDevices[1],
                BluetoothProfile.PRIORITY_OFF);

        // Make the current active device fail to connect
        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);
        when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);
        intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
        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);
        updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);

        // This device should be set to ON
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
@@ -260,8 +258,8 @@ public class PhonePolicyTest {
    }

    /**
     * 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
     * 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() {
@@ -291,18 +289,89 @@ public class PhonePolicyTest {

        // 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);
        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 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() {
        // Return a list of bonded devices (just one)
        BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
        bondedDevices[0] = TestUtils.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.getPriority(bondedDevices[0])).thenReturn(
                BluetoothProfile.PRIORITY_AUTO_CONNECT);
        when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
                BluetoothProfile.PRIORITY_AUTO_CONNECT);

        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(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);

        // 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(bondedDevices[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(
                bondedDevices[0]);

        // We send a connection failure event for the attempted profile, and keep the connected
        // profile connected.
        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.A2DP,
                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);

        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());

        // Verify no one changes the priority of the failed profile
        verify(mA2dpService, never()).setPriority(eq(bondedDevices[0]), anyInt());

        // Send a connection success event for one profile again without disconnecting all profiles
        updateProfileConnectionStateHelper(bondedDevices[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(
                bondedDevices[0]);

        // Send a disconnection event for all connected profiles
        hsConnectedDevices.remove(bondedDevices[0]);
        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);

        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());

        // Send a connection success event for one profile again to trigger re-connect
        hsConnectedDevices.add(bondedDevices[0]);
        updateProfileConnectionStateHelper(bondedDevices[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(
                bondedDevices[0]);
    }

    /**
     * Test that a second device will auto-connect if there is already one connected device.
     *
@@ -353,21 +422,13 @@ public class PhonePolicyTest {

        // 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, a2dpNotConnectedDevice1);
        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);
        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
        intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice2);
        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);
        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(
@@ -454,17 +515,12 @@ public class PhonePolicyTest {
        when(mA2dpService.getConnectionState(testDevices[3])).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);

        // Get the broadcast receiver to inject events
        BroadcastReceiver injector = mPhonePolicy.getBroadcastReceiver();

        // Generate connection state changed for HFP for testDevices[1] and trigger
        // auto-connect for A2DP.
        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[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);
        injector.onReceive(null /* context */, intent);
        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]));
@@ -495,12 +551,9 @@ public class PhonePolicyTest {

        // Generate connection state changed for A2DP for testDevices[2] and trigger
        // auto-connect for HFP.
        intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[2]);
        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
        injector.onReceive(null /* context */, intent);
        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]));
@@ -666,17 +719,11 @@ public class PhonePolicyTest {
                BluetoothProfile.STATE_DISCONNECTED);
        when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);
        when(mHeadsetService.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[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);
        updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);

        // Check that we don't get any calls to reconnect
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -709,4 +756,27 @@ public class PhonePolicyTest {
                eq(BluetoothProfile.PRIORITY_ON));
        verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
    }

    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);
    }
}