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

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

Merge "Redesign HFP Client call handling"

am: a5008e1a

Change-Id: Ibe73e9f82e00d10fd9693d68d318b292bdba94f4
parents e6c65c29 a5008e1a
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
@@ -344,12 +344,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
@@ -371,32 +371,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();
@@ -658,7 +640,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 &&
@@ -667,7 +649,7 @@ public class HeadsetClientService extends ProfileService {
        }

        Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL);
        msg.arg1 = index;
        msg.obj = uuid;
        mStateMachine.sendMessage(msg);
        return true;
    }
@@ -686,44 +668,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