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

Commit 43eceddf authored by Pavlin Radoslavov's avatar Pavlin Radoslavov Committed by android-build-merger
Browse files

Merge changes from topics "bt-a2dp-service-ok-to-connect",...

Merge changes from topics "bt-a2dp-service-ok-to-connect", "bt-a2dp-service-connect-sink-uuid-check"
am: aee17978

Change-Id: Idcad896b78791d965066fb086714a5b3d9d3c985
parents 14ac9300 aee17978
Loading
Loading
Loading
Loading
+48 −17
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.HandlerThread;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.support.annotation.GuardedBy;
import android.support.annotation.VisibleForTesting;
@@ -58,7 +57,10 @@ public class A2dpService extends ProfileService {
    private static final boolean DBG = true;
    private static final String TAG = "A2dpService";

    private static A2dpService sA2dpService;

    private BluetoothAdapter mAdapter;
    private AdapterService mAdapterService;
    private HandlerThread mStateMachinesThread;
    private Avrcp mAvrcp;

@@ -84,14 +86,6 @@ public class A2dpService extends ProfileService {
        mA2dpNativeInterface = A2dpNativeInterface.getInstance();
    }

    private static A2dpService sA2dpService;
    static final ParcelUuid[] A2DP_SOURCE_UUID = {
            BluetoothUuid.AudioSource
    };
    static final ParcelUuid[] A2DP_SOURCE_SINK_UUIDS = {
            BluetoothUuid.AudioSource, BluetoothUuid.AudioSink
    };

    @Override
    protected IProfileServiceBinder initBinder() {
        return new BluetoothA2dpBinder(this);
@@ -103,9 +97,9 @@ public class A2dpService extends ProfileService {
            Log.d(TAG, "start()");
        }

        AdapterService adapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
                "AdapterService cannot be null when A2dpService starts");
        mMaxConnectedAudioDevices = adapterService.getMaxConnectedAudioDevices();
        mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
        if (DBG) {
            Log.d(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
        }
@@ -215,9 +209,8 @@ public class A2dpService extends ProfileService {
        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
            return false;
        }
        ParcelUuid[] featureUuids = device.getUuids();
        if ((BluetoothUuid.containsAnyUuid(featureUuids, A2DP_SOURCE_UUID))
                && !(BluetoothUuid.containsAllUuids(featureUuids, A2DP_SOURCE_SINK_UUIDS))) {
        if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
                                         BluetoothUuid.AudioSink)) {
            Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
            return false;
        }
@@ -292,14 +285,52 @@ public class A2dpService extends ProfileService {
        return (connected < mMaxConnectedAudioDevices);
    }

    /**
     * Check whether can connect to a peer device.
     * The check considers a number of factors during the evaluation.
     *
     * @param device the peer device to connect to
     * @return true if connection is allowed, otherwise false
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public boolean okToConnect(BluetoothDevice device) {
        // Check if this is an incoming connection in Quiet mode.
        if (mAdapterService.isQuietModeEnabled()) {
            Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
            return false;
        }
        // Check if too many devices
        if (!canConnectToDevice(device)) {
            Log.e(TAG, "okToConnect: cannot connect to " + device
                    + " : too many connected devices");
            return false;
        }
        // Check priority and accept or reject the connection
        int priority = getPriority(device);
        int bondState = mAdapterService.getBondState(device);
        // If priority is undefined, it is likely that our SDP has not completed and peer is
        // initiating the connection. Allow the connection only if the device is bonded or bonding.
        if ((priority == BluetoothProfile.PRIORITY_UNDEFINED)
                && (bondState == BluetoothDevice.BOND_NONE)) {
            Log.e(TAG, "okToConnect: cannot connect to " + device + " : priority=" + priority
                    + " bondState=" + bondState);
            return false;
        }
        if (priority <= BluetoothProfile.PRIORITY_OFF) {
            Log.e(TAG, "okToConnect: cannot connect to " + device + " : priority=" + priority);
            return false;
        }
        return true;
    }

    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        List<BluetoothDevice> devices = new ArrayList<>();
        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
        synchronized (mStateMachines) {
            for (BluetoothDevice device : bondedDevices) {
                ParcelUuid[] featureUuids = device.getUuids();
                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
                if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
                                                 BluetoothUuid.AudioSink)) {
                    continue;
                }
                int connectionState = BluetoothProfile.STATE_DISCONNECTED;
@@ -659,7 +690,7 @@ public class A2dpService extends ProfileService {
            if (DBG) {
                Log.d(TAG, "Creating a new state machine for " + device);
            }
            sm = A2dpStateMachine.make(device, this, this, mA2dpNativeInterface,
            sm = A2dpStateMachine.make(device, this, mA2dpNativeInterface,
                                       mStateMachinesThread.getLooper());
            mStateMachines.put(device, sm);
            return sm;
+17 −44
Original line number Diff line number Diff line
@@ -50,14 +50,12 @@ import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.VisibleForTesting;
import android.util.Log;

import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -88,18 +86,18 @@ final class A2dpStateMachine extends StateMachine {
    private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
    private int mLastConnectionState = -1;

    private A2dpService mService;
    private A2dpService mA2dpService;
    private A2dpNativeInterface mA2dpNativeInterface;

    private final BluetoothDevice mDevice;
    private boolean mIsPlaying = false;
    private BluetoothCodecStatus mCodecStatus;

    A2dpStateMachine(BluetoothDevice device, A2dpService svc, Context context,
    A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService,
                     A2dpNativeInterface a2dpNativeInterface, Looper looper) {
        super(TAG, looper);
        mDevice = device;
        mService = svc;
        mA2dpService = a2dpService;
        mA2dpNativeInterface = a2dpNativeInterface;

        mDisconnected = new Disconnected();
@@ -115,14 +113,12 @@ final class A2dpStateMachine extends StateMachine {
        setInitialState(mDisconnected);
    }

    static A2dpStateMachine make(BluetoothDevice device, A2dpService svc,
                                 Context context, A2dpNativeInterface a2dpNativeInterface,
                                 Looper looper) {
    static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService,
                                 A2dpNativeInterface a2dpNativeInterface, Looper looper) {
        if (DBG) {
            Log.d(TAG, "make for device " + device);
        }
        A2dpStateMachine a2dpSm = new A2dpStateMachine(device, svc, context,
                                                       a2dpNativeInterface,
        A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface,
                                                       looper);
        a2dpSm.start();
        return a2dpSm;
@@ -159,7 +155,7 @@ final class A2dpStateMachine extends StateMachine {
                if (mIsPlaying) {
                    Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
                    mIsPlaying = false;
                    mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
                    mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
                    broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                        BluetoothA2dp.STATE_PLAYING);
                }
@@ -189,7 +185,7 @@ final class A2dpStateMachine extends StateMachine {
                        Log.e(TAG, "Disconnected: error connecting to " + mDevice);
                        break;
                    }
                    if (okToConnect(mDevice)) {
                    if (mA2dpService.okToConnect(mDevice)) {
                        transitionTo(mConnecting);
                        sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
                    } else {
@@ -233,7 +229,7 @@ final class A2dpStateMachine extends StateMachine {
                    Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice);
                    break;
                case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
                    if (okToConnect(mDevice)) {
                    if (mA2dpService.okToConnect(mDevice)) {
                        Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice);
                        transitionTo(mConnecting);
                    } else {
@@ -244,7 +240,7 @@ final class A2dpStateMachine extends StateMachine {
                    break;
                case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
                    Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice);
                    if (okToConnect(mDevice)) {
                    if (mA2dpService.okToConnect(mDevice)) {
                        Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice);
                        transitionTo(mConnected);
                    } else {
@@ -438,7 +434,7 @@ final class A2dpStateMachine extends StateMachine {
                    transitionTo(mDisconnected);
                    break;
                case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
                    if (okToConnect(mDevice)) {
                    if (mA2dpService.okToConnect(mDevice)) {
                        Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
                        transitionTo(mConnected);
                    } else {
@@ -448,7 +444,7 @@ final class A2dpStateMachine extends StateMachine {
                    }
                    break;
                case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
                    if (okToConnect(mDevice)) {
                    if (mA2dpService.okToConnect(mDevice)) {
                        Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
                        transitionTo(mConnecting);
                    } else {
@@ -570,7 +566,7 @@ final class A2dpStateMachine extends StateMachine {
                        if (!mIsPlaying) {
                            Log.i(TAG, "Connected: started playing: " + mDevice);
                            mIsPlaying = true;
                            mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
                            broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
                                                BluetoothA2dp.STATE_NOT_PLAYING);
                        }
@@ -582,7 +578,7 @@ final class A2dpStateMachine extends StateMachine {
                        if (mIsPlaying) {
                            Log.i(TAG, "Connected: stopped playing: " + mDevice);
                            mIsPlaying = false;
                            mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
                            broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                                BluetoothA2dp.STATE_PLAYING);
                        }
@@ -646,30 +642,7 @@ final class A2dpStateMachine extends StateMachine {

        boolean sameAudioFeedingParameters =
                newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig);
        mService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
    }

    boolean okToConnect(BluetoothDevice device) {
        AdapterService adapterService = AdapterService.getAdapterService();
        int priority = mService.getPriority(device);
        // Check if this is an incoming connection in Quiet mode.
        if ((adapterService == null) || adapterService.isQuietModeEnabled()) {
            return false;
        }
        // Check if too many devices
        if (!mService.canConnectToDevice(device)) {
            Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
            return false;
        }
        // Check priority and accept or reject the connection. If priority is undefined
        // it is likely that our SDP has not completed and peer is initiating the
        // connection. Allow this connection, provided the device is bonded.
        if ((BluetoothProfile.PRIORITY_OFF < priority) || (
                (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState()
                        != BluetoothDevice.BOND_NONE))) {
            return true;
        }
        return false;
        mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
    }

    // This method does not check for error conditon (newState == prevState)
@@ -685,7 +658,7 @@ final class A2dpStateMachine extends StateMachine {
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
        mA2dpService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
    }

    private void broadcastAudioState(int newState, int prevState) {
@@ -700,7 +673,7 @@ final class A2dpStateMachine extends StateMachine {
        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        mService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
        mA2dpService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
    }

    private static String messageWhatToString(int what) {
+22 −0
Original line number Diff line number Diff line
@@ -22,11 +22,13 @@ import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Looper;
import android.os.ParcelUuid;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ServiceTestRule;
@@ -100,6 +102,8 @@ public class A2dpServiceTest {
        // Get a device for testing
        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED);
        doReturn(new ParcelUuid[]{BluetoothUuid.AudioSink}).when(mAdapterService)
                .getRemoteUuids(any(BluetoothDevice.class));
    }

    @After
@@ -196,6 +200,24 @@ public class A2dpServiceTest {
                            mA2dpService.getPriority(mTestDevice));
    }

    /**
     * Test that an outgoing connection to device that does not have A2DP Sink UUID is rejected
     */
    @Test
    public void testOutgoingConnectMissingAudioSinkUuid() {
        // Update the device priority so okToConnect() returns true
        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));

        // Return AudioSource UUID instead of AudioSink
        doReturn(new ParcelUuid[]{BluetoothUuid.AudioSource}).when(mAdapterService)
                .getRemoteUuids(any(BluetoothDevice.class));

        // Send a connect request
        Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
    }

    /**
     * Test that an outgoing connection times out
     */
+2 −12
Original line number Diff line number Diff line
@@ -76,8 +76,7 @@ public class A2dpStateMachineTest {
        mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
        mHandlerThread.start();
        mA2dpStateMachine = new A2dpStateMachine(mTestDevice, mA2dpService,
                                                 mTargetContext, mA2dpNativeInterface,
                                                 mHandlerThread.getLooper());
                                                 mA2dpNativeInterface, mHandlerThread.getLooper());
        // Override the timeout value to speed up the test
        A2dpStateMachine.sConnectTimeoutMs = 1000;     // 1s
        mA2dpStateMachine.start();
@@ -108,16 +107,7 @@ public class A2dpStateMachineTest {
     * @param allow if true, connection is allowed
     */
    private void allowConnection(boolean allow) {
        if (allow) {
            // Update the device priority so okToConnect() returns true
            doReturn(BluetoothProfile.PRIORITY_ON).when(mA2dpService)
                    .getPriority(any(BluetoothDevice.class));
        } else {
            // Update the device priority so okToConnect() returns false
            doReturn(BluetoothProfile.PRIORITY_OFF).when(mA2dpService)
                    .getPriority(any(BluetoothDevice.class));
        }
        doReturn(true).when(mA2dpService).canConnectToDevice(any(BluetoothDevice.class));
        doReturn(allow).when(mA2dpService).okToConnect(any(BluetoothDevice.class));
    }

    /**