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

Commit 8b9e8741 authored by Sanket Agarwal's avatar Sanket Agarwal Committed by Android (Google) Code Review
Browse files

Merge "HFP is exposed via Telecom ConnectionService." into nyc-dev

parents 41093afc a9ad98ec
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -342,5 +342,13 @@
                <action android:name="android.bluetooth.IBluetoothHeadsetClient" />
            </intent-filter>
        </service>
        <service android:name=".hfpclient.connserv.HfpClientConnectionService"
                 android:permission="android.permission.BIND_CONNECTION_SERVICE"
                 android:enabled="@bool/profile_supported_hfpclient">
            <intent-filter>
                <!-- Mechanism for Telecom stack to connect -->
                <action android:name="android.telecom.ConnectionService" />
            </intent-filter>
        </service>
    </application>
</manifest>
+22 −3
Original line number Diff line number Diff line
@@ -51,13 +51,15 @@ import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.telecom.TelecomManager;

import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;

import java.util.ArrayList;
import java.util.Arrays;
@@ -155,6 +157,7 @@ final class HeadsetClientStateMachine extends StateMachine {
    private boolean mAudioWbs;
    private final BluetoothAdapter mAdapter;
    private boolean mNativeAvailable;
    private TelecomManager mTelecomManager;

    // currently connected device
    private BluetoothDevice mCurrentDevice = null;
@@ -318,6 +321,7 @@ final class HeadsetClientStateMachine extends StateMachine {
    }

    private void sendCallChangedIntent(BluetoothHeadsetClientCall c) {
        Log.d(TAG, "sendCallChangedIntent " + c);
        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
        intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
@@ -1214,6 +1218,8 @@ final class HeadsetClientStateMachine extends StateMachine {
        mAudioRouteAllowed = context.getResources().getBoolean(
                R.bool.headset_client_initial_audio_route_allowed);

        mTelecomManager = (TelecomManager) context.getSystemService(context.TELECOM_SERVICE);

        mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
        mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
        mIndicatorNetworkSignal = 0;
@@ -2344,6 +2350,19 @@ 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 −0
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.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHeadsetClientCall;
import android.os.Bundle;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.util.Log;

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

public class HfpClientConference extends Conference {
    private static final String TAG = "HfpClientConference";

    private BluetoothDevice mDevice;
    private BluetoothHeadsetClient mHeadsetProfile;

    public HfpClientConference(PhoneAccountHandle handle,
            BluetoothDevice device, BluetoothHeadsetClient client) {
        super(handle);
        mDevice = device;
        mHeadsetProfile = client;
        boolean manage = HfpClientConnectionService.hasHfpClientEcc(client, device);
        setConnectionCapabilities(Connection.CAPABILITY_SUPPORT_HOLD |
                Connection.CAPABILITY_HOLD |
                (manage ? Connection.CAPABILITY_MANAGE_CONFERENCE : 0));
        setActive();
    }

    @Override
    public void onDisconnect() {
        Log.d(TAG, "onDisconnect");
        mHeadsetProfile.terminateCall(mDevice, 0);
        setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
    }

    @Override
    public void onMerge(Connection connection) {
        Log.d(TAG, "onMerge " + connection);
        addConnection(connection);
    }

    @Override
    public void onSeparate(Connection connection) {
        Log.d(TAG, "onSeparate " + connection);
        ((HfpClientConnection) connection).enterPrivateMode();
        removeConnection(connection);
    }

    @Override
    public void onHold() {
        Log.d(TAG, "onHold");
        mHeadsetProfile.holdCall(mDevice);
    }

    @Override
    public void onUnhold() {
        if (getPrimaryConnection().getConnectionService()
                .getAllConnections().size() > 1) {
            Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
            return;
        }
        Log.d(TAG, "onUnhold");
        mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
    }

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

    @Override
    public void onConnectionAdded(Connection connection) {
        Log.d(TAG, "onConnectionAdded " + connection);
        if (connection.getState() == Connection.STATE_HOLDING &&
                getState() == Connection.STATE_ACTIVE) {
            connection.onAnswer();
        } else if (connection.getState() == Connection.STATE_ACTIVE &&
                getState() == Connection.STATE_HOLDING) {
            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
        }
    }
}
+235 −0
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.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHeadsetClientCall;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.net.Uri;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.TelecomManager;
import android.util.Log;

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

    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;

    public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client,
            BluetoothHeadsetClientCall call, Uri number) {
        mDevice = device;
        mContext = context;
        mHeadsetProfile = client;
        mCurrentCall = call;
        if (mHeadsetProfile != null) {
            mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
        }
        setAudioModeIsVoip(false);
        setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
        setInitializing();

        if (mHeadsetProfile != null) {
            finishInitializing();
        }
    }

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

    public void onHfpDisconnected() {
        mHeadsetProfile = null;
        close(DisconnectCause.ERROR);
    }

    public void onAdded() {
        mAdded = true;
    }

    public BluetoothHeadsetClientCall getCall() {
        return mCurrentCall;
    }

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

    public void enterPrivateMode() {
        mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
        setActive();
    }

    public void handleCallChanged(BluetoothHeadsetClientCall call) {
        HfpClientConference conference = (HfpClientConference) getConference();
        mCurrentCall = call;

        int state = call.getState();
        Log.d(TAG, "Got call state change to " + state);
        switch (state) {
            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
                setActive();
                if (conference != null) {
                    conference.setActive();
                }
                break;
            case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
            case BluetoothHeadsetClientCall.CALL_STATE_HELD:
                setOnHold();
                if (conference != null) {
                    conference.setOnHold();
                }
                break;
            case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
            case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
                setDialing();
                break;
            case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
            case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
                setRinging();
                break;
            case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
                // TODO Use more specific causes
                close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
                break;
            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);
            setInitialized();
            mHeadsetProfile.dial(mDevice, number);
        } else {
            handleCallChanged(mCurrentCall);
        }
    }

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

        mClosed = true;
        mCurrentCall = null;

        destroy();
    }

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

    @Override
    public void onDisconnect() {
        Log.d(TAG, "onDisconnect " + mCurrentCall);
        if (!mClosed) {
            if (mHeadsetProfile != null && mCurrentCall != null) {
                mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
                mLocalDisconnect = true;
            } else {
                close(DisconnectCause.LOCAL);
            }
        }
    }

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

    @Override
    public void onHold() {
        Log.d(TAG, "onHold " + mCurrentCall);
        if (!mClosed && mHeadsetProfile != null) {
            mHeadsetProfile.holdCall(mDevice);
        }
    }

    @Override
    public void onUnhold() {
        if (getConnectionService().getAllConnections().size() > 1) {
            Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
            return;
        }
        Log.d(TAG, "onUnhold " + mCurrentCall);
        if (!mClosed && mHeadsetProfile != null) {
            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
        }
    }

    @Override
    public void onAnswer() {
        Log.d(TAG, "onAnswer " + mCurrentCall);
        if (!mClosed) {
            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
        }
    }

    @Override
    public void onReject() {
        Log.d(TAG, "onReject " + mCurrentCall);
        if (!mClosed) {
            mHeadsetProfile.rejectCall(mDevice);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof HfpClientConnection)) {
            return false;
        }
        Uri otherAddr = ((HfpClientConnection) o).getAddress();
        return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
    }

    @Override
    public int hashCode() {
        return getAddress() == null ? 0 : getAddress().hashCode();
    }

    @Override
    public String toString() {
        return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
                mCurrentCall + "}";
    }
}
+412 −0

File added.

Preview size limit exceeded, changes collapsed.