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

Commit cbcfc800 authored by Sanket Agarwal's avatar Sanket Agarwal Committed by android-build-merger
Browse files

Improve connection handling logic in ConnService

am: 2a32ce99

Change-Id: I042b3a58fe83a76252096ba1db18a69a4a1c2f54
parents 39224d81 2a32ce99
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -347,6 +347,7 @@ public class HeadsetClientService extends ProfileService {
        public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
            HeadsetClientService service = getService();
            if (service == null) {
                Log.w(TAG, "service is null");
                return false;
            }
            return service.terminateCall(device, call.getUUID());
+18 −10
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ import com.android.bluetooth.R;

final class HeadsetClientStateMachine extends StateMachine {
    private static final String TAG = "HeadsetClientStateMachine";
    private static final boolean DBG = false;
    private static final boolean DBG = true;

    static final int NO_ACTION = 0;

@@ -111,11 +111,13 @@ final class HeadsetClientStateMachine extends StateMachine {

    public static final Integer HF_ORIGINATED_CALL_ID = new Integer(-1);
    private long OUTGOING_TIMEOUT_MILLI = 10 * 1000; // 10 seconds
    private long QUERY_CURRENT_CALLS_WAIT_MILLIS = 2 * 1000; // 2 seconds

    private final Disconnected mDisconnected;
    private final Connecting mConnecting;
    private final Connected mConnected;
    private final AudioOn mAudioOn;
    private long mClccTimer = 0;

    private final HeadsetClientService mService;

@@ -464,7 +466,7 @@ final class HeadsetClientStateMachine extends StateMachine {
        }

        if (loopQueryCalls()) {
            sendMessageDelayed(QUERY_CURRENT_CALLS, 1523);
            sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
        }

        mCallsUpdate.clear();
@@ -1315,7 +1317,19 @@ final class HeadsetClientStateMachine extends StateMachine {
                    }
                    break;
                case QUERY_CURRENT_CALLS:
                    // Whenever the timer expires we query calls if there are outstanding requests
                    // for query calls.
                    long currentElapsed = SystemClock.elapsedRealtime();
                    if (mClccTimer < currentElapsed) {
                        queryCallsStart();
                        mClccTimer = currentElapsed + QUERY_CURRENT_CALLS_WAIT_MILLIS;
                        // Request satisfied, ignore all other call query messages.
                        removeMessages(QUERY_CURRENT_CALLS);
                    } else {
                        // Replace all messages with one concrete message.
                        removeMessages(QUERY_CURRENT_CALLS);
                        sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
                    }
                    break;
                case STACK_EVENT:
                    Intent intent = null;
@@ -1531,13 +1545,7 @@ final class HeadsetClientStateMachine extends StateMachine {
                                case TERMINATE_SPECIFIC_CALL:
                                    // if terminating specific succeed no other
                                    // event is send
                                    if (event.valueInt == BluetoothHeadsetClient.ACTION_RESULT_OK) {
                                        BluetoothHeadsetClientCall sc =
                                                (BluetoothHeadsetClientCall) queuedAction.second;
                                        setCallState(sc,
                                                BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
                                        mCalls.remove(sc.getId());
                                    } else {
                                    if (event.valueInt != BluetoothHeadsetClient.ACTION_RESULT_OK) {
                                        sendActionResultIntent(event);
                                    }
                                    break;
+76 −36
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context;
import android.net.Uri;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
import android.util.Log;

@@ -30,38 +31,64 @@ import java.util.UUID;

public class HfpClientConnection extends Connection {
    private static final String TAG = "HfpClientConnection";
    private static final boolean DBG = true;

    private final Context mContext;
    private final BluetoothDevice mDevice;

    private BluetoothHeadsetClient mHeadsetProfile;

    private BluetoothHeadsetClientCall mCurrentCall;
    private boolean mClosed;
    private boolean mLocalDisconnect;
    private boolean mClientHasEcc;
    private boolean mAdded;

    // Constructor to be used when there's an existing call (such as that created on the AG or
    // when connection happens and we see calls for the first time).
    public HfpClientConnection(Context context, BluetoothDevice device,
            BluetoothHeadsetClient client, BluetoothHeadsetClientCall call, Uri number) {
            BluetoothHeadsetClient client, BluetoothHeadsetClientCall call) {
        mDevice = device;
        mContext = context;
        mHeadsetProfile = client;

        setInitializing();
        if (call == null) {
            throw new IllegalStateException("Call is null");
        }

        if (call != null) {
        mCurrentCall = call;
        handleCallChanged();
        } else if (mHeadsetProfile != null) {
        finishInitializing();
    }

    // Constructor to be used when a call is intiated on the HF. The call handle is obtained by
    // using the dial() command.
    public HfpClientConnection(Context context, BluetoothDevice device,
            BluetoothHeadsetClient client, Uri number) {
        mDevice = device;
        mContext = context;
        mHeadsetProfile = client;

        if (mHeadsetProfile == null) {
            throw new IllegalStateException("HeadsetProfile is null, returning");
        }

        mCurrentCall = mHeadsetProfile.dial(
            mDevice, number.getSchemeSpecificPart());
        if (mCurrentCall == null) {
            close(DisconnectCause.ERROR);
            Log.e(TAG, "Failed to create the call, dial failed.");
            return;
        }

        setInitializing();
        setDialing();
        finishInitializing();
    }

        if (mHeadsetProfile != null) {
    void finishInitializing() {
        mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
        }
        setAudioModeIsVoip(false);
        Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, mCurrentCall.getNumber(), null);
        setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
@@ -72,12 +99,6 @@ public class HfpClientConnection extends Connection {
        return mCurrentCall.getUUID();
    }

    public void onHfpConnected(BluetoothHeadsetClient client) {
        mHeadsetProfile = client;
        mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
        handleCallChanged();
    }

    public void onHfpDisconnected() {
        mHeadsetProfile = null;
        close(DisconnectCause.ERROR);
@@ -92,7 +113,7 @@ public class HfpClientConnection extends Connection {
    }

    public boolean inConference() {
        return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
        return mAdded && mCurrentCall.isMultiParty() &&
                getState() != Connection.STATE_DISCONNECTED;
    }

@@ -102,6 +123,10 @@ public class HfpClientConnection extends Connection {
    }

    public void updateCall(BluetoothHeadsetClientCall call) {
        if (call == null) {
            Log.e(TAG, "Updating call to a null value.");
            return;
        }
        mCurrentCall = call;
    }

@@ -109,7 +134,9 @@ public class HfpClientConnection extends Connection {
        HfpClientConference conference = (HfpClientConference) getConference();
        int state = mCurrentCall.getState();

        if (DBG) {
            Log.d(TAG, "Got call state change to " + state);
        }
        switch (state) {
            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
                setActive();
@@ -142,11 +169,12 @@ public class HfpClientConnection extends Connection {
    }

    private void close(int cause) {
        if (DBG) {
            Log.d(TAG, "Closing " + mClosed);
        }
        if (mClosed) {
            return;
        }

        setDisconnected(new DisconnectCause(cause));

        mClosed = true;
@@ -157,34 +185,40 @@ public class HfpClientConnection extends Connection {

    @Override
    public void onPlayDtmfTone(char c) {
        if (DBG) {
            Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
        if (!mClosed && mHeadsetProfile != null) {
        }
        if (!mClosed) {
            mHeadsetProfile.sendDTMF(mDevice, (byte) c);
        }
    }

    @Override
    public synchronized void onDisconnect() {
        Log.d(TAG, "onDisconnect " + mCurrentCall);
        // In this state we can close the call without problems.
        if (mHeadsetProfile != null) {
        if (DBG) {
            Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed);
        }
        // The call is not closed so we should send a terminate here.
        if (!mClosed) {
            mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
            mLocalDisconnect = true;
        } else if (mCurrentCall == null) {
            Log.w(TAG, "Call disconnected but call handle not received.");
        }
    }

    @Override
    public void onAbort() {
        if (DBG) {
            Log.d(TAG, "onAbort " + mCurrentCall);
        }
        onDisconnect();
    }

    @Override
    public void onHold() {
        if (DBG) {
            Log.d(TAG, "onHold " + mCurrentCall);
        if (!mClosed && mHeadsetProfile != null) {
        }
        if (!mClosed) {
            mHeadsetProfile.holdCall(mDevice);
        }
    }
@@ -195,15 +229,19 @@ public class HfpClientConnection extends Connection {
            Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
            return;
        }
        if (DBG) {
            Log.d(TAG, "onUnhold " + mCurrentCall);
        if (!mClosed && mHeadsetProfile != null) {
        }
        if (!mClosed) {
            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
        }
    }

    @Override
    public void onAnswer() {
        if (DBG) {
            Log.d(TAG, "onAnswer " + mCurrentCall);
        }
        if (!mClosed) {
            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
        }
@@ -211,7 +249,9 @@ public class HfpClientConnection extends Connection {

    @Override
    public void onReject() {
        if (DBG) {
            Log.d(TAG, "onReject " + mCurrentCall);
        }
        if (!mClosed) {
            mHeadsetProfile.rejectCall(mDevice);
        }
+92 −32
Original line number Diff line number Diff line
@@ -69,7 +69,9 @@ public class HfpClientConnectionService extends ConnectionService {
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DBG) {
                Log.d(TAG, "onReceive " + intent);
            }
            String action = intent != null ? intent.getAction() : null;

            if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
@@ -77,7 +79,9 @@ public class HfpClientConnectionService extends ConnectionService {
                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);

                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    if (DBG) {
                        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.
@@ -105,7 +109,9 @@ public class HfpClientConnectionService extends ConnectionService {
                    if (mHeadsetProfile != null) {
                        List<BluetoothHeadsetClientCall> calls =
                                mHeadsetProfile.getCurrentCalls(mDevice);
                        if (DBG) {
                            Log.d(TAG, "Got calls " + calls);
                        }
                        if (calls == null) {
                            // We can get null as a return if we are not connected. Hence there may
                            // be a race in getting the broadcast and HFP Client getting
@@ -120,11 +126,15 @@ public class HfpClientConnectionService extends ConnectionService {
                        Log.e(TAG, "headset profile is null, ignoring broadcast.");
                    }
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    if (DBG) {
                        Log.d(TAG, "Disconnecting from " + device);
                    }
                    // Disconnect any inflight calls from the connection service.
                    synchronized (HfpClientConnectionService.this) {
                        if (device.equals(mDevice)) {
                            if (DBG) {
                                Log.d(TAG, "Resetting state for " + device);
                            }
                            mDevice = null;
                            disconnectAll();
                            mTelecomManager.unregisterPhoneAccount(
@@ -138,15 +148,19 @@ public class HfpClientConnectionService extends ConnectionService {
                // be added (see ACTION_CONNECTION_STATE_CHANGED intent above).
                handleCall((BluetoothHeadsetClientCall)
                        intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL));
                if (DBG) {
                    Log.d(TAG, mConnections.size() + " remaining");
                }
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        if (DBG) {
            Log.d(TAG, "onCreate");
        }
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
        mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT);
@@ -154,7 +168,9 @@ public class HfpClientConnectionService extends ConnectionService {

    @Override
    public void onDestroy() {
        if (DBG) {
            Log.d(TAG, "onDestroy called");
        }
        // Close the profile.
        if (mHeadsetProfile != null) {
            mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile);
@@ -180,7 +196,9 @@ public class HfpClientConnectionService extends ConnectionService {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (DBG) {
            Log.d(TAG, "onStartCommand " + intent);
        }
        // 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.
@@ -206,9 +224,11 @@ public class HfpClientConnectionService extends ConnectionService {
    }

    private void handleCall(BluetoothHeadsetClientCall call) {
        Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
        Log.d(TAG, "Got call " + call.toString(true) + "/" + number);
        if (DBG) {
            Log.d(TAG, "Got call " + call.toString(true));
        }
        HfpClientConnection connection = findConnectionKey(call.getUUID());

        if (connection != null) {
            connection.updateCall(call);
            connection.handleCallChanged();
@@ -216,7 +236,7 @@ public class HfpClientConnectionService extends ConnectionService {

        if (connection == null) {
            // Create the connection here, trigger Telecom to bind to us.
            buildConnection(call.getDevice(), call, number);
            buildConnection(call.getDevice(), call, null);

            PhoneAccountHandle handle = getHandle();
            TelecomManager manager =
@@ -228,7 +248,8 @@ public class HfpClientConnectionService extends ConnectionService {
            Bundle b = new Bundle();
            if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_DIALING ||
                call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ALERTING ||
                call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
                call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE ||
                call.getState() == BluetoothHeadsetClientCall.CALL_STATE_WAITING) {
                // This is an outgoing call. Even if it is an active call we do not have a way of
                // putting that parcelable in a seaprate field.
                b.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call);
@@ -239,7 +260,9 @@ public class HfpClientConnectionService extends ConnectionService {
                manager.addNewIncomingCall(handle, b);
            }
        } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
            Log.d(TAG, "Removing number " + number);
            if (DBG) {
                Log.d(TAG, "Removing call " + call);
            }
            synchronized (this) {
                mConnections.remove(call.getUUID());
            }
@@ -252,7 +275,10 @@ public class HfpClientConnectionService extends ConnectionService {
    public Connection onCreateIncomingConnection(
            PhoneAccountHandle connectionManagerAccount,
            ConnectionRequest request) {
        Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
        if (DBG) {
            Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount +
                " req: " + request);
        }
        if (connectionManagerAccount != null &&
                !getHandle().equals(connectionManagerAccount)) {
            Log.w(TAG, "HfpClient does not support having a connection manager");
@@ -285,7 +311,9 @@ public class HfpClientConnectionService extends ConnectionService {
    public Connection onCreateOutgoingConnection(
            PhoneAccountHandle connectionManagerAccount,
            ConnectionRequest request) {
        if (DBG) {
            Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
        }
        if (connectionManagerAccount != null &&
                !getHandle().equals(connectionManagerAccount)) {
            Log.w(TAG, "HfpClient does not support having a connection manager");
@@ -294,7 +322,9 @@ public class HfpClientConnectionService extends ConnectionService {

        HfpClientConnection connection =
                buildConnection(getDevice(request.getAccountHandle()), null, request.getAddress());
        if (connection != null) {
            connection.onAdded();
        }
        return connection;
    }

@@ -305,7 +335,9 @@ public class HfpClientConnectionService extends ConnectionService {
    public Connection onCreateUnknownConnection(
            PhoneAccountHandle connectionManagerAccount,
            ConnectionRequest request) {
        if (DBG) {
            Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
        }
        if (connectionManagerAccount != null &&
                !getHandle().equals(connectionManagerAccount)) {
            Log.w(TAG, "HfpClient does not support having a connection manager");
@@ -336,7 +368,9 @@ public class HfpClientConnectionService extends ConnectionService {

    @Override
    public void onConference(Connection connection1, Connection connection2) {
        if (DBG) {
            Log.d(TAG, "onConference " + connection1 + " " + connection2);
        }
        if (mConference == null) {
            BluetoothDevice device = getDevice(getHandle());
            mConference = new HfpClientConference(getHandle(), device, mHeadsetProfile);
@@ -426,6 +460,7 @@ public class HfpClientConnectionService extends ConnectionService {
        for (HfpClientConnection connection : mConnections.values()) {
            connection.onHfpDisconnected();
        }

        mConnections.clear();
        if (mConference != null) {
            mConference.destroy();
@@ -441,24 +476,47 @@ public class HfpClientConnectionService extends ConnectionService {

    private synchronized HfpClientConnection buildConnection(
            BluetoothDevice device, BluetoothHeadsetClientCall call, Uri number) {
        if (mHeadsetProfile == null) {
            Log.e(TAG, "Cannot create connection for call " + call + " when Profile not available");
            return null;
        }

        if (call == null && number == null) {
            Log.e(TAG, "Both call and number cannot be null.");
            return null;
        }

        if (DBG) {
            Log.d(TAG, "Creating connection on " + device + " for " + call + "/" + number);
        HfpClientConnection connection =
                new HfpClientConnection(this, device, mHeadsetProfile, call, number);
        }
        HfpClientConnection connection = null;
        if (call != null) {
            connection = new HfpClientConnection(this, device, mHeadsetProfile, call);
        } else {
            connection = new HfpClientConnection(this, device, mHeadsetProfile, number);
        }
        if (connection.getState() != Connection.STATE_DISCONNECTED) {
            mConnections.put(connection.getUUID(), connection);
        }

        return connection;
    }

    BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if (DBG) {
                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.
                if (DBG) {
                    Log.d(TAG, "Creating phone account.");
                }
                synchronized (HfpClientConnectionService.this) {
                    mDevice = devices.get(0);
                    mDevicePhoneAccount = getAccount(HfpClientConnectionService.this, mDevice);
@@ -470,12 +528,10 @@ public class HfpClientConnectionService extends ConnectionService {
                }
            }

            for (HfpClientConnection connection : mConnections.values()) {
                connection.onHfpConnected(mHeadsetProfile);
            }

            List<BluetoothHeadsetClientCall> calls = mHeadsetProfile.getCurrentCalls(mDevice);
            if (DBG) {
                Log.d(TAG, "Got calls " + calls);
            }
            if (calls != null) {
                for (BluetoothHeadsetClientCall call : calls) {
                    handleCall(call);
@@ -490,7 +546,9 @@ public class HfpClientConnectionService extends ConnectionService {

        @Override
        public void onServiceDisconnected(int profile) {
            if (DBG) {
                Log.d(TAG, "onServiceDisconnected " + profile);
            }
            mHeadsetProfile = null;
            disconnectAll();
        }
@@ -517,7 +575,9 @@ public class HfpClientConnectionService extends ConnectionService {
                    .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
                    .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                    .build();
        if (DBG) {
            Log.d(TAG, "phoneaccount: " + account);
        }
        return account;
    }
}