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

Commit 4d489e55 authored by Jack He's avatar Jack He
Browse files

PhonePolicy: Gate CONNECT_OTHER_PROFILE per device

* When A2DP or HFP is connected for a device, PhonePolicy will try to
  connect to other profiles such as HFP, A2DP and PAN in case the remote
  device did not connect these profiles themselves.
* De-duping this effort by checking if PhonePolicy handler already has
  the CONNECT_OTHER_PROFILES message  works for a single device but
  won't work when multiple devices are connecting at the same time
  from the remote side. Only the first connected device's connection
  request with be honored and other devices' connection request might be
  dropped if the first device's connection request is still in progress
* Therefore, we need to create a HashSet to gate this message per
  BluetoothDevice

Bug: 79938868
Test: runtest -j40 bluetooth
Change-Id: I3865404c16b0243e610c8a94e3d32459107581a8
(cherry picked from commit a5e75bcd70bba16b7701ea5f08ac3816d82d318a)
parent 40bfd245
Loading
Loading
Loading
Loading
+19 −9
Original line number Diff line number Diff line
@@ -80,13 +80,15 @@ class PhonePolicy {
    private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;

    // Timeouts
    private static final int CONNECT_OTHER_PROFILES_TIMEOUT = 6000; // 6s
    @VisibleForTesting
    static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s

    private final AdapterService mAdapterService;
    private final ServiceFactory mFactory;
    private final Handler mHandler;
    private final HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>();
    private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
    private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>();

    // Broadcast receiver for all changes to states of various profiles
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -167,12 +169,14 @@ class PhonePolicy {
                }
                break;

                case MESSAGE_CONNECT_OTHER_PROFILES:
                case MESSAGE_CONNECT_OTHER_PROFILES: {
                    // Called when we try connect some profiles in processConnectOtherProfiles but
                    // we send a delayed message to try connecting the remaining profiles
                    processConnectOtherProfiles((BluetoothDevice) msg.obj);
                    BluetoothDevice device = (BluetoothDevice) msg.obj;
                    processConnectOtherProfiles(device);
                    mConnectOtherProfilesDeviceSet.remove(device);
                    break;

                }
                case MESSAGE_ADAPTER_STATE_TURNED_ON:
                    // Call auto connect when adapter switches state to ON
                    resetStates();
@@ -341,12 +345,18 @@ class PhonePolicy {
    }

    private void connectOtherProfile(BluetoothDevice device) {
        if ((!mHandler.hasMessages(MESSAGE_CONNECT_OTHER_PROFILES))
                && (!mAdapterService.isQuietModeEnabled())) {
        if (mAdapterService.isQuietModeEnabled()) {
            debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
            return;
        }
        if (mConnectOtherProfilesDeviceSet.contains(device)) {
            debugLog("connectOtherProfile: already scheduled callback for " + device);
            return;
        }
        mConnectOtherProfilesDeviceSet.add(device);
        Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
        m.obj = device;
            mHandler.sendMessageDelayed(m, CONNECT_OTHER_PROFILES_TIMEOUT);
        }
        mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
    }

    // This function is called whenever a profile is connected.  This allows any other bluetooth
+57 −73
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ import android.content.BroadcastReceiver;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.ParcelUuid;
import android.os.TestLooperManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;

@@ -50,11 +48,15 @@ import java.util.ArrayList;
@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 / 2;

    private HandlerThread mHandlerThread;
    private TestLooperManager mTestLooperManager;
    private BluetoothAdapter mAdapter;
    private BluetoothDevice mTestDevice;
    private PhonePolicy mPhonePolicy;

    @Mock private AdapterService mAdapterService;
    @Mock private ServiceFactory mServiceFactory;
@@ -74,20 +76,19 @@ public class PhonePolicyTest {
        // Start handler thread for this test
        mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread");
        mHandlerThread.start();
        mTestLooperManager = InstrumentationRegistry.getInstrumentation()
                .acquireLooperManager(mHandlerThread.getLooper());
        // Mock the looper
        doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper();
        // 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();
        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
        mTestDevice = TestUtils.getTestDevice(mAdapter, 1);
        PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS;
        mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory);
    }

    @After
    public void tearDown() throws Exception {
        mTestLooperManager.release();
        mHandlerThread.quit();
        TestUtils.clearAdapterService(mAdapterService);
    }
@@ -106,26 +107,19 @@ public class PhonePolicyTest {
        // Mock the A2DP service to return undefined priority
        when(mA2dpService.getPriority(mTestDevice)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);

        PhonePolicy phPol = new PhonePolicy(mAdapterService, mServiceFactory);

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

        // Inject an event for UUIDs updated for a remote device with only HFP enabled
        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mTestDevice);
        ParcelUuid[] uuids = new ParcelUuid[2];
        uuids[0] = BluetoothUuid.Handsfree;
        uuids[1] = BluetoothUuid.AudioSink;

        intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
        injector.onReceive(null /* context */, intent);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);

        // Check that the priorities of the devices for preferred profiles are set to ON
        executePendingMessages(1);
        verify(mHeadsetService, times(1)).setPriority(eq(mTestDevice),
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(mTestDevice),
                eq(BluetoothProfile.PRIORITY_ON));
        verify(mA2dpService, times(1)).setPriority(eq(mTestDevice),
        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(mTestDevice),
                eq(BluetoothProfile.PRIORITY_ON));
    }

@@ -152,20 +146,14 @@ public class PhonePolicyTest {
        when(mA2dpService.getPriority(mTestDevice)).thenReturn(
                BluetoothProfile.PRIORITY_AUTO_CONNECT);

        PhonePolicy phPol = new PhonePolicy(mAdapterService, mServiceFactory);

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

        // 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);
        injector.onReceive(null /* context */, intent);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);

        // Check that we got a request to connect over HFP and A2DP
        executePendingMessages(1);
        verify(mHeadsetService, times(1)).connect(eq(mTestDevice));
        verify(mA2dpService, times(1)).connect(eq(mTestDevice));
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(mTestDevice));
        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(mTestDevice));
    }

    /**
@@ -188,8 +176,6 @@ public class PhonePolicyTest {

        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);

        PhonePolicy phPol = new PhonePolicy(mAdapterService, mServiceFactory);

        // 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
@@ -200,9 +186,6 @@ public class PhonePolicyTest {
        when(mA2dpService.getConnectionState(mTestDevice)).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);

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

        // 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);
@@ -210,11 +193,11 @@ public class PhonePolicyTest {
        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);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);

        // Check that we get a call to A2DP connect
        executePendingMessages(2);
        verify(mA2dpService, times(1)).connect(eq(mTestDevice));
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                eq(mTestDevice));
    }

    /**
@@ -222,11 +205,12 @@ public class PhonePolicyTest {
     */
    @Test
    public void testAutoConnectMultipleDevices() {
        final int kMaxTestDevices = 2;
        final int kMaxTestDevices = 3;
        BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
        ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
        ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
        BluetoothDevice a2dpNotConnectedDevice = null;
        BluetoothDevice a2dpNotConnectedDevice1 = null;
        BluetoothDevice a2dpNotConnectedDevice2 = null;

        for (int i = 0; i < kMaxTestDevices; i++) {
            BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
@@ -243,36 +227,46 @@ public class PhonePolicyTest {
            // of connected devices.
            hsConnectedDevices.add(testDevice);
            // Connect A2DP for all devices except the last one
            if (i < kMaxTestDevices - 1) {
            if (i < (kMaxTestDevices - 2)) {
                a2dpConnectedDevices.add(testDevice);
            } else {
                a2dpNotConnectedDevice = 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);
        // One of the A2DP devices is not connected
        when(mA2dpService.getConnectionState(a2dpNotConnectedDevice)).thenReturn(
        // Two of the A2DP devices are not connected
        when(mA2dpService.getConnectionState(a2dpNotConnectedDevice1)).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);
        when(mA2dpService.getConnectionState(a2dpNotConnectedDevice2)).thenReturn(
                BluetoothProfile.STATE_DISCONNECTED);

        // Get the broadcast receiver to inject events
        PhonePolicy phPol = new PhonePolicy(mAdapterService, mServiceFactory);
        BroadcastReceiver injector = phPol.getBroadcastReceiver();

        // 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, a2dpNotConnectedDevice);
        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);
        injector.onReceive(null /* context */, intent);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);

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

        // Check that we get a call to A2DP connect
        executePendingMessages(2);
        verify(mA2dpService, times(1)).connect(eq(a2dpNotConnectedDevice));
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                eq(a2dpNotConnectedDevice1));
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                eq(a2dpNotConnectedDevice2));
    }

    /**
@@ -312,22 +306,19 @@ public class PhonePolicyTest {
                hsConnectedDevices.add(testDevice);
                when(mHeadsetService.getPriority(testDevice)).thenReturn(
                        BluetoothProfile.PRIORITY_ON);
                when(mA2dpService.getPriority(testDevice)).thenReturn(
                        BluetoothProfile.PRIORITY_ON);
                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
            }
            if (i == 2) {
                a2dpConnectedDevices.add(testDevice);
                when(mHeadsetService.getPriority(testDevice)).thenReturn(
                        BluetoothProfile.PRIORITY_ON);
                when(mA2dpService.getPriority(testDevice)).thenReturn(
                        BluetoothProfile.PRIORITY_ON);
                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
            }
            if (i == 3) {
                // Device not connected
                when(mHeadsetService.getPriority(testDevice)).thenReturn(
                        BluetoothProfile.PRIORITY_ON);
                when(mA2dpService.getPriority(testDevice)).thenReturn(
                        BluetoothProfile.PRIORITY_ON);
                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
            }
        }
        when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
@@ -369,8 +360,8 @@ public class PhonePolicyTest {
        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
        injector.onReceive(null /* context */, intent);
        // Check that we get a call to A2DP connect
        executePendingMessages(2);
        verify(mA2dpService, times(1)).connect(eq(testDevices[1]));
        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                eq(testDevices[1]));

        // testDevices[1] auto-connect completed for A2DP
        a2dpConnectedDevices.add(testDevices[1]);
@@ -404,8 +395,8 @@ public class PhonePolicyTest {
        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
        injector.onReceive(null /* context */, intent);
        // Check that we get a call to HFP connect
        executePendingMessages(2);
        verify(mHeadsetService, times(1)).connect(eq(testDevices[2]));
        verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
                eq(testDevices[2]));

        // testDevices[2] auto-connect completed for HFP
        hsConnectedDevices.add(testDevices[2]);
@@ -473,8 +464,8 @@ public class PhonePolicyTest {
        injector.onReceive(null /* context */, intent);

        // Check that we don't get any calls to reconnect
        executePendingMessages(1);
        verify(mA2dpService, never()).connect(eq(mTestDevice));
        verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
                eq(mTestDevice));
        verify(mHeadsetService, never()).connect(eq(mTestDevice));
    }

@@ -504,17 +495,10 @@ public class PhonePolicyTest {
        injector.onReceive(null /* context */, intent);

        // Check that we do not crash and not call any setPriority methods
        executePendingMessages(1);
        verify(mHeadsetService, never()).setPriority(eq(mTestDevice),
                eq(BluetoothProfile.PRIORITY_ON));
        verify(mHeadsetService,
                after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).setPriority(
                eq(mTestDevice), eq(BluetoothProfile.PRIORITY_ON));
        verify(mA2dpService, never()).setPriority(eq(mTestDevice),
                eq(BluetoothProfile.PRIORITY_ON));
    }

    private void executePendingMessages(int numMessage) {
        while (numMessage > 0) {
            mTestLooperManager.execute(mTestLooperManager.next());
            numMessage--;
        }
    }
}