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

Commit f5f73e1d authored by Sanket Agarwal's avatar Sanket Agarwal Committed by Gerrit Code Review
Browse files

Merge "Redesign HFP Client call handling"

parents 5d200aa1 19e98fd1
Loading
Loading
Loading
Loading
+15 −55
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ import com.android.bluetooth.Utils;

import java.util.ArrayList;
import java.util.List;

import java.util.UUID;

/**
 * Provides Bluetooth Headset Client (HF Role) profile, as a service in the
@@ -342,12 +342,12 @@ public class HeadsetClientService extends ProfileService {
        }

        @Override
        public boolean terminateCall(BluetoothDevice device, int index) {
        public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
            HeadsetClientService service = getService();
            if (service == null) {
                return false;
            }
            return service.terminateCall(device, index);
            return service.terminateCall(device, call.getUUID());
        }

        @Override
@@ -369,32 +369,14 @@ public class HeadsetClientService extends ProfileService {
        }

        @Override
        public boolean redial(BluetoothDevice device) {
        public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
            HeadsetClientService service = getService();
            if (service == null) {
                return false;
            }
            return service.redial(device);
        }

        @Override
        public boolean dial(BluetoothDevice device, String number) {
            HeadsetClientService service = getService();
            if (service == null) {
                return false;
                return null;
            }
            return service.dial(device, number);
        }

        @Override
        public boolean dialMemory(BluetoothDevice device, int location) {
            HeadsetClientService service = getService();
            if (service == null) {
                return false;
            }
            return service.dialMemory(device, location);
        }

        @Override
        public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
            HeadsetClientService service = getService();
@@ -654,7 +636,7 @@ public class HeadsetClientService extends ProfileService {
        return true;
    }

    boolean terminateCall(BluetoothDevice device, int index) {
    boolean terminateCall(BluetoothDevice device, UUID uuid) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        int connectionState = mStateMachine.getConnectionState(device);
        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
@@ -663,7 +645,7 @@ public class HeadsetClientService extends ProfileService {
        }

        Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL);
        msg.arg1 = index;
        msg.obj = uuid;
        mStateMachine.sendMessage(msg);
        return true;
    }
@@ -682,44 +664,22 @@ public class HeadsetClientService extends ProfileService {
        return true;
    }

    boolean redial(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        int connectionState = mStateMachine.getConnectionState(device);
        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
                connectionState != BluetoothProfile.STATE_CONNECTING) {
            return false;
        }

        Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.REDIAL);
        mStateMachine.sendMessage(msg);
        return true;
    }

    boolean dial(BluetoothDevice device, String number) {
    BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        int connectionState = mStateMachine.getConnectionState(device);
        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
                connectionState != BluetoothProfile.STATE_CONNECTING) {
            return false;
            return null;
        }

        BluetoothHeadsetClientCall call = new BluetoothHeadsetClientCall(
            device, HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID,
            BluetoothHeadsetClientCall.CALL_STATE_DIALING, number, false  /* multiparty */,
            true  /* outgoing */);
        Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.DIAL_NUMBER);
        msg.obj = number;
        msg.obj = call;
        mStateMachine.sendMessage(msg);
        return true;
    }

    boolean dialMemory(BluetoothDevice device, int location) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        int connectionState = mStateMachine.getConnectionState(device);
        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
                connectionState != BluetoothProfile.STATE_CONNECTING) {
            return false;
        }
        Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.DIAL_MEMORY);
        msg.arg1 = location;
        mStateMachine.sendMessage(msg);
        return true;
        return call;
    }

    public boolean sendDTMF(BluetoothDevice device, byte code) {
+409 −730

File changed.

Preview size limit exceeded, changes collapsed.

+0 −108
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.bluetooth.hfpclient.connserv;

import android.bluetooth.BluetoothHeadsetClientCall;
import android.net.Uri;
import android.telecom.PhoneAccount;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;

/* Matching a call to internal state requires understanding of the ph number and the state of the
 * remote itself. The best way to associate a call with remote is to use the Call IDs that are
 * passed by the HFP AG role. But consider the scenario when even before a call is notified to the
 * remote and it tries to get back to us -- we execute a full cycle of Call -> Hangup. In such case
 * we have no recourse but to use phone number as the key. This class implements the matching logic
 * for phone numbers & IDs. It identifies uniquely a {@link HfpClientConnection}.
 */
class ConnectionKey {
    /* Initialize with invalid values */
    public static final int INVALID_ID = -1;

    private final int mId;
    private final String mPhoneNumber;

    ConnectionKey(int id, Uri phoneNumber) {
        if (id == INVALID_ID && phoneNumber == null) {
            throw new IllegalStateException("invalid id and phone number");
        }
        mId = id;
        mPhoneNumber = normalizePhoneUri(phoneNumber);
    }

    private static String normalizePhoneUri(Uri phoneNumber) {
        // Sometimes the number can be rewritten with brackets and such, we normalize to take that
        // factor out.
        String schemeSpecificPart = phoneNumber.getSchemeSpecificPart();
        if (!TextUtils.isEmpty(schemeSpecificPart)) {
            if (schemeSpecificPart.startsWith("+")) {
                schemeSpecificPart = schemeSpecificPart.substring(1);
            }

            schemeSpecificPart = PhoneNumberUtils.normalizeNumber(schemeSpecificPart);
        }
        return schemeSpecificPart;
    }

    public static ConnectionKey getKey(BluetoothHeadsetClientCall call) {
        if (call == null) {
            throw new IllegalStateException("call may not be null");
        }

        // IDs in the call start with *1*.
        int id = call.getId() > 0 ? call.getId() : INVALID_ID;
        Uri phoneNumberUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
        return new ConnectionKey(id, phoneNumberUri);
    }

    public int getId() {
        return mId;
    }

    public String getPhoneNumber() {
        return mPhoneNumber;
    }

    @Override
    public String toString() {
        return "Key " + getId() + " Phone number " + getPhoneNumber();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ConnectionKey)) {
            return false;
        }

        ConnectionKey candidate = (ConnectionKey) o;

        /* Match based on IDs */
        if (getId() != INVALID_ID && candidate.getId() != INVALID_ID) {
            return (getId() == candidate.getId());
        }

        /* Otherwise match has to be based on phone numbers */
        return (getPhoneNumber() != null && candidate.getPhoneNumber() != null &&
                getPhoneNumber().equals(candidate.getPhoneNumber()));
    }

    @Override
    public int hashCode() {
        // Since we may do partial match based on either ID or phone numbers hence put all the items
        // in same bucket.
        return 0;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ public class HfpClientConference extends Conference {
    @Override
    public void onDisconnect() {
        Log.d(TAG, "onDisconnect");
        mHeadsetProfile.terminateCall(mDevice, 0);
        mHeadsetProfile.terminateCall(mDevice, null);
        setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
    }

+35 −51
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.telecom.DisconnectCause;
import android.telecom.TelecomManager;
import android.util.Log;

import java.util.UUID;

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

@@ -34,34 +36,46 @@ public class HfpClientConnection extends Connection {

    private BluetoothHeadsetClient mHeadsetProfile;
    private BluetoothHeadsetClientCall mCurrentCall;
    private boolean mClosing;
    private boolean mClosed;
    private boolean mLocalDisconnect;
    private boolean mClientHasEcc;
    private boolean mAdded;

    public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client,
            BluetoothHeadsetClientCall call, Uri number) {
    public HfpClientConnection(Context context, BluetoothDevice device,
            BluetoothHeadsetClient client, BluetoothHeadsetClientCall call, Uri number) {
        mDevice = device;
        mContext = context;
        mHeadsetProfile = client;

        setInitializing();

        if (call != null) {
            mCurrentCall = call;
            handleCallChanged();
        } else if (mHeadsetProfile != null) {
            mCurrentCall = mHeadsetProfile.dial(
                mDevice, number.getSchemeSpecificPart());
            setDialing();
        }

        if (mHeadsetProfile != null) {
            mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
        }
        setAudioModeIsVoip(false);
        setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
        setInitialized();

        if (mHeadsetProfile != null) {
            finishInitializing();
        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
                (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
    }

    public UUID getUUID() {
        return mCurrentCall.getUUID();
    }

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

    public void onHfpDisconnected() {
@@ -87,27 +101,14 @@ public class HfpClientConnection extends Connection {
        setActive();
    }

    public void handleCallChanged(BluetoothHeadsetClientCall call) {
        HfpClientConference conference = (HfpClientConference) getConference();
    public void updateCall(BluetoothHeadsetClientCall call) {
        mCurrentCall = call;
        int state = call.getState();

        // If this call is already terminated (locally) but we are only hearing about the handle of
        // the call right now -- then close the call.
        boolean closing = false;
        synchronized (this) {
            closing = mClosing;
        }
        if (closing && state != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
            if (mHeadsetProfile != null) {
                mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
                mLocalDisconnect = true;
            } else {
                Log.e(TAG, "HFP disconnected but call update received, ignore.");
            }
            return;
    }

    public void handleCallChanged() {
        HfpClientConference conference = (HfpClientConference) getConference();
        int state = mCurrentCall.getState();

        Log.d(TAG, "Got call state change to " + state);
        switch (state) {
            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
@@ -138,21 +139,6 @@ public class HfpClientConnection extends Connection {
            default:
                Log.wtf(TAG, "Unexpected phone state " + state);
        }
        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
                (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
    }

    private void finishInitializing() {
        if (mCurrentCall == null) {
            String number = getAddress().getSchemeSpecificPart();
            Log.d(TAG, "Dialing " + number);
            mHeadsetProfile.dial(mDevice, number);
            setDialing();
            // We will change state dependent on broadcasts from BluetoothHeadsetClientCall.
        } else {
            handleCallChanged(mCurrentCall);
        }
    }

    private void close(int cause) {
@@ -160,6 +146,7 @@ public class HfpClientConnection extends Connection {
        if (mClosed) {
            return;
        }

        setDisconnected(new DisconnectCause(cause));

        mClosed = true;
@@ -179,17 +166,14 @@ public class HfpClientConnection extends Connection {
    @Override
    public synchronized void onDisconnect() {
        Log.d(TAG, "onDisconnect " + mCurrentCall);
        mClosing = true;
        if (!mClosed) {
        // In this state we can close the call without problems.
            if (mHeadsetProfile != null && mCurrentCall != null) {
                mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
        if (mHeadsetProfile != null) {
            mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
            mLocalDisconnect = true;
        } else if (mCurrentCall == null) {
            Log.w(TAG, "Call disconnected but call handle not received.");
        }
    }
    }

    @Override
    public void onAbort() {
Loading