HfpClientConnection.java revision 05462fe44531f887fe91ac173ec645e91b236025
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.bluetooth.hfpclient.connserv;
17
18import android.bluetooth.BluetoothDevice;
19import android.bluetooth.BluetoothHeadsetClient;
20import android.bluetooth.BluetoothHeadsetClientCall;
21import android.bluetooth.BluetoothProfile;
22import android.content.Context;
23import android.net.Uri;
24import android.telecom.Connection;
25import android.telecom.DisconnectCause;
26import android.telecom.TelecomManager;
27import android.util.Log;
28
29import java.util.UUID;
30
31public class HfpClientConnection extends Connection {
32    private static final String TAG = "HfpClientConnection";
33
34    private final Context mContext;
35    private final BluetoothDevice mDevice;
36
37    private BluetoothHeadsetClient mHeadsetProfile;
38    private BluetoothHeadsetClientCall mCurrentCall;
39    private boolean mClosed;
40    private boolean mLocalDisconnect;
41    private boolean mClientHasEcc;
42    private boolean mAdded;
43
44    public HfpClientConnection(Context context, BluetoothDevice device,
45            BluetoothHeadsetClient client, BluetoothHeadsetClientCall call, Uri number) {
46        mDevice = device;
47        mContext = context;
48        mHeadsetProfile = client;
49
50        setInitializing();
51
52        if (call != null) {
53            mCurrentCall = call;
54            handleCallChanged();
55        } else if (mHeadsetProfile != null) {
56            mCurrentCall = mHeadsetProfile.dial(
57                mDevice, number.getSchemeSpecificPart());
58            setDialing();
59        }
60
61        if (mHeadsetProfile != null) {
62            mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
63        }
64        setAudioModeIsVoip(false);
65        setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
66        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
67                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
68                (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
69    }
70
71    public UUID getUUID() {
72        return mCurrentCall.getUUID();
73    }
74
75    public void onHfpConnected(BluetoothHeadsetClient client) {
76        mHeadsetProfile = client;
77        mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
78        handleCallChanged();
79    }
80
81    public void onHfpDisconnected() {
82        mHeadsetProfile = null;
83        close(DisconnectCause.ERROR);
84    }
85
86    public void onAdded() {
87        mAdded = true;
88    }
89
90    public BluetoothHeadsetClientCall getCall() {
91        return mCurrentCall;
92    }
93
94    public boolean inConference() {
95        return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
96                getState() != Connection.STATE_DISCONNECTED;
97    }
98
99    public void enterPrivateMode() {
100        mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
101        setActive();
102    }
103
104    public void updateCall(BluetoothHeadsetClientCall call) {
105        mCurrentCall = call;
106    }
107
108    public void handleCallChanged() {
109        HfpClientConference conference = (HfpClientConference) getConference();
110        int state = mCurrentCall.getState();
111
112        Log.d(TAG, "Got call state change to " + state);
113        switch (state) {
114            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
115                setActive();
116                if (conference != null) {
117                    conference.setActive();
118                }
119                break;
120            case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
121            case BluetoothHeadsetClientCall.CALL_STATE_HELD:
122                setOnHold();
123                if (conference != null) {
124                    conference.setOnHold();
125                }
126                break;
127            case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
128            case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
129                setDialing();
130                break;
131            case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
132            case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
133                setRinging();
134                break;
135            case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
136                // TODO Use more specific causes
137                close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
138                break;
139            default:
140                Log.wtf(TAG, "Unexpected phone state " + state);
141        }
142    }
143
144    private void close(int cause) {
145        Log.d(TAG, "Closing " + mClosed);
146        if (mClosed) {
147            return;
148        }
149
150        setDisconnected(new DisconnectCause(cause));
151
152        mClosed = true;
153        mCurrentCall = null;
154
155        destroy();
156    }
157
158    @Override
159    public void onPlayDtmfTone(char c) {
160        Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
161        if (!mClosed && mHeadsetProfile != null) {
162            mHeadsetProfile.sendDTMF(mDevice, (byte) c);
163        }
164    }
165
166    @Override
167    public synchronized void onDisconnect() {
168        Log.d(TAG, "onDisconnect " + mCurrentCall);
169        // In this state we can close the call without problems.
170        if (mHeadsetProfile != null) {
171            mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
172            mLocalDisconnect = true;
173        } else if (mCurrentCall == null) {
174            Log.w(TAG, "Call disconnected but call handle not received.");
175        }
176    }
177
178    @Override
179    public void onAbort() {
180        Log.d(TAG, "onAbort " + mCurrentCall);
181        onDisconnect();
182    }
183
184    @Override
185    public void onHold() {
186        Log.d(TAG, "onHold " + mCurrentCall);
187        if (!mClosed && mHeadsetProfile != null) {
188            mHeadsetProfile.holdCall(mDevice);
189        }
190    }
191
192    @Override
193    public void onUnhold() {
194        if (getConnectionService().getAllConnections().size() > 1) {
195            Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
196            return;
197        }
198        Log.d(TAG, "onUnhold " + mCurrentCall);
199        if (!mClosed && mHeadsetProfile != null) {
200            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
201        }
202    }
203
204    @Override
205    public void onAnswer() {
206        Log.d(TAG, "onAnswer " + mCurrentCall);
207        if (!mClosed) {
208            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
209        }
210    }
211
212    @Override
213    public void onReject() {
214        Log.d(TAG, "onReject " + mCurrentCall);
215        if (!mClosed) {
216            mHeadsetProfile.rejectCall(mDevice);
217        }
218    }
219
220    @Override
221    public boolean equals(Object o) {
222        if (!(o instanceof HfpClientConnection)) {
223            return false;
224        }
225        Uri otherAddr = ((HfpClientConnection) o).getAddress();
226        return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
227    }
228
229    @Override
230    public int hashCode() {
231        return getAddress() == null ? 0 : getAddress().hashCode();
232    }
233
234    @Override
235    public String toString() {
236        return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
237                mCurrentCall + "}";
238    }
239}
240