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

Commit 7de93b45 authored by Sanket Agarwal's avatar Sanket Agarwal
Browse files

HfpClientConnectionService does not synchronize state

Currently active device is a synchronizable state and the current
implementation was simply waiving it. Also the mechanism to create a
phone account was invalid since the PhoneAccountHandle was not unique
per device. PhoneAccountHandle not being unique is OK for now but fails
in case of multiple devices.

Bug: b/28068989
Change-Id: I729df5aa110a428b8ce23cf6658d81c5acbc16a2
parent 96ad32db
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -32,10 +32,12 @@ import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
import java.util.List;


/**
 * Provides Bluetooth Headset Client (HF Role) profile, as a service in the
 * Bluetooth application.
@@ -49,6 +51,8 @@ public class HeadsetClientService extends ProfileService {
    private HeadsetClientStateMachine mStateMachine;
    private static HeadsetClientService sHeadsetClientService;

    public static String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";

    @Override
    protected String getName() {
        return TAG;
@@ -70,6 +74,12 @@ public class HeadsetClientService extends ProfileService {
            Log.w(TAG, "Unable to register broadcat receiver", e);
        }
        setHeadsetClientService(this);

        // Start the HfpClientConnectionService to create connection with telecom when HFP
        // connection is available.
        Intent startIntent = new Intent(this, HfpClientConnectionService.class);
        startService(startIntent);

        return true;
    }

@@ -81,6 +91,12 @@ public class HeadsetClientService extends ProfileService {
            Log.w(TAG, "Unable to unregister broadcast receiver", e);
        }
        mStateMachine.doQuit();

        // Stop the HfpClientConnectionService.
        Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
        stopIntent.putExtra(HFP_CLIENT_STOP_TAG, true);
        startService(stopIntent);

        return true;
    }

+0 −14
Original line number Diff line number Diff line
@@ -2350,21 +2350,7 @@ final class HeadsetClientStateMachine extends StateMachine {
                    HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
            }

            // If we are connected to HFP AG, then register the phone account so that telecom can
            // make calls via HFP.
            mTelecomManager.registerPhoneAccount(
                HfpClientConnectionService.getAccount(mService, device));
            mTelecomManager.enablePhoneAccount(
                HfpClientConnectionService.getAccount(mService, device).getAccountHandle(), true);
            mTelecomManager.setUserSelectedOutgoingPhoneAccount(
                HfpClientConnectionService.getHandle(mService));
            mService.startService(new Intent(mService, HfpClientConnectionService.class));
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            mTelecomManager.unregisterPhoneAccount(HfpClientConnectionService.getHandle(mService));
            mService.stopService(new Intent(mService, HfpClientConnectionService.class));
        }

        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
    }

+105 −22
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.util.Log;

import com.android.bluetooth.hfpclient.HeadsetClientService;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
@@ -49,7 +51,11 @@ public class HfpClientConnectionService extends ConnectionService {
    public static final String HFP_SCHEME = "hfpc";

    private BluetoothAdapter mAdapter;
    // Currently active device.
    private BluetoothDevice mDevice;
    // Phone account associated with the above device.
    private PhoneAccount mDevicePhoneAccount;
    // BluetoothHeadset proxy.
    private BluetoothHeadsetClient mHeadsetProfile;
    private TelecomManager mTelecomManager;

@@ -65,11 +71,34 @@ public class HfpClientConnectionService extends ConnectionService {
            String action = intent != null ? intent.getAction() : null;

            if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);

                Log.d(TAG, "Established connection with " + mDevice);
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.d(TAG, "Established connection with " + device);
                    synchronized (HfpClientConnectionService.this) {
                        if (device.equals(mDevice)) {
                            // We are already connected and this message can be safeuly ignored.
                            Log.w(TAG, "Got connected for previously connected device, ignoring.");
                        } else {
                            // Since we are connected to a new device close down the previous
                            // account and register the new one.
                            if (mDevicePhoneAccount != null) {
                                mTelecomManager.unregisterPhoneAccount(
                                    mDevicePhoneAccount.getAccountHandle());
                            }
                            // Reset the device and the phone account associated.
                            mDevice = device;
                            mDevicePhoneAccount =
                                getAccount(HfpClientConnectionService.this, device);
                            mTelecomManager.registerPhoneAccount(mDevicePhoneAccount);
                            mTelecomManager.enablePhoneAccount(
                                mDevicePhoneAccount.getAccountHandle(), true);
                            mTelecomManager.setUserSelectedOutgoingPhoneAccount(
                                mDevicePhoneAccount.getAccountHandle());
                        }
                    }

                    // Add any existing calls to the telecom stack.
                    if (mHeadsetProfile != null) {
                        List<BluetoothHeadsetClientCall> calls =
@@ -81,8 +110,18 @@ public class HfpClientConnectionService extends ConnectionService {
                    } else {
                    }
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.d(TAG, "Disconnecting from " + device);
                    // Disconnect any inflight calls from the connection service.
                    synchronized (HfpClientConnectionService.this) {
                        if (device.equals(mDevice)) {
                            Log.d(TAG, "Resetting state for " + device);
                            mDevice = null;
                            disconnectAll();
                            mTelecomManager.unregisterPhoneAccount(
                                mDevicePhoneAccount.getAccountHandle());
                            mDevicePhoneAccount = null;
                        }
                    }
                }
            } else if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) {
                // If we are not connected, then when we actually do get connected -- the calls should
@@ -105,22 +144,48 @@ public class HfpClientConnectionService extends ConnectionService {

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy called");
        // Close the profile.
        if (mHeadsetProfile != null) {
            mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile);
        }

        // Unregister the broadcast receiver.
        try {
            unregisterReceiver(mBroadcastReceiver);
        } catch (IllegalArgumentException ex) {
            Log.w(TAG, "Receiver was not registered.");
        }

        // Unregister the phone account. This should ideally happen when disconnection ensues but in
        // case the service crashes we may need to force clean.
        synchronized (this) {
            mDevice = null;
            if (mDevicePhoneAccount != null) {
                mTelecomManager.unregisterPhoneAccount(mDevicePhoneAccount.getAccountHandle());
                mDevicePhoneAccount = null;
            }
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand " + intent);
        String action = intent != null ? intent.getAction() : null;
        // In order to make sure that the service is sticky (recovers from errors when HFP
        // connection is still active) and to stop it we need a special intent since stopService
        // only recreates it.
        if (intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, false)) {
            // Stop the service.
            stopSelf();
            return 0;
        } else {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
            filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
            registerReceiver(mBroadcastReceiver, filter);
            return START_STICKY;
        }
    }

    private void handleCall(BluetoothHeadsetClientCall call) {
        Log.d(TAG, "Got call " + call);
@@ -135,7 +200,7 @@ public class HfpClientConnectionService extends ConnectionService {
            // Create the connection here, trigger Telecom to bind to us.
            buildConnection(call.getDevice(), call, number);

            PhoneAccountHandle handle = HfpClientConnectionService.getHandle(this);
            PhoneAccountHandle handle = getHandle();
            TelecomManager manager =
                    (TelecomManager) getSystemService(Context.TELECOM_SERVICE);

@@ -169,7 +234,7 @@ public class HfpClientConnectionService extends ConnectionService {
            ConnectionRequest request) {
        Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
        if (connectionManagerAccount != null &&
                !getHandle(this).equals(connectionManagerAccount)) {
                !getHandle().equals(connectionManagerAccount)) {
            Log.w(TAG, "HfpClient does not support having a connection manager");
            return null;
        }
@@ -199,7 +264,7 @@ public class HfpClientConnectionService extends ConnectionService {
            ConnectionRequest request) {
        Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
        if (connectionManagerAccount != null &&
                !getHandle(this).equals(connectionManagerAccount)) {
                !getHandle().equals(connectionManagerAccount)) {
            Log.w(TAG, "HfpClient does not support having a connection manager");
            return null;
        }
@@ -219,7 +284,7 @@ public class HfpClientConnectionService extends ConnectionService {
            ConnectionRequest request) {
        Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
        if (connectionManagerAccount != null &&
                !getHandle(this).equals(connectionManagerAccount)) {
                !getHandle().equals(connectionManagerAccount)) {
            Log.w(TAG, "HfpClient does not support having a connection manager");
            return null;
        }
@@ -246,8 +311,8 @@ public class HfpClientConnectionService extends ConnectionService {
    public void onConference(Connection connection1, Connection connection2) {
        Log.d(TAG, "onConference " + connection1 + " " + connection2);
        if (mConference == null) {
            BluetoothDevice device = getDevice(getHandle(this));
            mConference = new HfpClientConference(getHandle(this), device, mHeadsetProfile);
            BluetoothDevice device = getDevice(getHandle());
            mConference = new HfpClientConference(getHandle(), device, mHeadsetProfile);
            addConference(mConference);
        }
        mConference.setActive();
@@ -287,8 +352,8 @@ public class HfpClientConnectionService extends ConnectionService {
            connection.setConferenceableConnections(held);
        }
        if (group.size() > 1 && mConference == null) {
            BluetoothDevice device = getDevice(getHandle(this));
            mConference = new HfpClientConference(getHandle(this), device, mHeadsetProfile);
            BluetoothDevice device = getDevice(getHandle());
            mConference = new HfpClientConference(getHandle(), device, mHeadsetProfile);
            if (group.get(0).getState() == Connection.STATE_ACTIVE) {
                mConference.setActive();
            } else {
@@ -361,6 +426,22 @@ public class HfpClientConnectionService extends ConnectionService {
            Log.d(TAG, "onServiceConnected");
            mHeadsetProfile = (BluetoothHeadsetClient) proxy;

            List<BluetoothDevice> devices = mHeadsetProfile.getConnectedDevices();
            if (devices == null || devices.size() != 1) {
                Log.w(TAG, "No connected or more than one connected devices found." + devices);
            } else { // We have exactly one device connected.
                Log.d(TAG, "Creating phone account.");
                synchronized (HfpClientConnectionService.this) {
                    mDevice = devices.get(0);
                    mDevicePhoneAccount = getAccount(HfpClientConnectionService.this, mDevice);
                    mTelecomManager.registerPhoneAccount(mDevicePhoneAccount);
                    mTelecomManager.enablePhoneAccount(
                        mDevicePhoneAccount.getAccountHandle(), true);
                    mTelecomManager.setUserSelectedOutgoingPhoneAccount(
                        mDevicePhoneAccount.getAccountHandle());
                }
            }

            for (HfpClientConnection connection : mConnections.values()) {
                connection.onHfpConnected(mHeadsetProfile);
            }
@@ -393,15 +474,17 @@ public class HfpClientConnectionService extends ConnectionService {
                features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, false);
    }

    public static PhoneAccountHandle getHandle(Context context) {
        return new PhoneAccountHandle(
                new ComponentName(context, HfpClientConnectionService.class), "hfp");
    public synchronized PhoneAccountHandle getHandle() {
        if (mDevicePhoneAccount == null) throw new IllegalStateException("Handle null??");
        return mDevicePhoneAccount.getAccountHandle();
    }

    public static PhoneAccount getAccount(Context context, BluetoothDevice device) {
        Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null);
        PhoneAccountHandle handle = new PhoneAccountHandle(
            new ComponentName(context, HfpClientConnectionService.class), device.getAddress());
        PhoneAccount account =
                new PhoneAccount.Builder(HfpClientConnectionService.getHandle(context), "HFP")
                new PhoneAccount.Builder(handle, "HFP")
                    .setAddress(addr)
                    .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
                    .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)