HfpClientConnection.java revision 57a102e5548a7a395bcd7feec3c1df6d61be8187
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
29public class HfpClientConnection extends Connection {
30    private static final String TAG = "HfpClientConnection";
31
32    private final Context mContext;
33    private final BluetoothDevice mDevice;
34
35    private BluetoothHeadsetClient mHeadsetProfile;
36    private BluetoothHeadsetClientCall mCurrentCall;
37    private boolean mClosing;
38    private boolean mClosed;
39    private boolean mLocalDisconnect;
40    private boolean mClientHasEcc;
41    private boolean mAdded;
42
43    public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client,
44            BluetoothHeadsetClientCall call, Uri number) {
45        mDevice = device;
46        mContext = context;
47        mHeadsetProfile = client;
48        mCurrentCall = call;
49        if (mHeadsetProfile != null) {
50            mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
51        }
52        setAudioModeIsVoip(false);
53        setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
54        setInitialized();
55
56        if (mHeadsetProfile != null) {
57            finishInitializing();
58        }
59    }
60
61    public void onHfpConnected(BluetoothHeadsetClient client) {
62        mHeadsetProfile = client;
63        mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
64        finishInitializing();
65    }
66
67    public void onHfpDisconnected() {
68        mHeadsetProfile = null;
69        close(DisconnectCause.ERROR);
70    }
71
72    public void onAdded() {
73        mAdded = true;
74    }
75
76    public BluetoothHeadsetClientCall getCall() {
77        return mCurrentCall;
78    }
79
80    public boolean inConference() {
81        return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
82                getState() != Connection.STATE_DISCONNECTED;
83    }
84
85    public void enterPrivateMode() {
86        mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
87        setActive();
88    }
89
90    public void handleCallChanged(BluetoothHeadsetClientCall call) {
91        HfpClientConference conference = (HfpClientConference) getConference();
92        mCurrentCall = call;
93        int state = call.getState();
94
95        // If this call is already terminated (locally) but we are only hearing about the handle of
96        // the call right now -- then close the call.
97        boolean closing = false;
98        synchronized (this) {
99            closing = mClosing;
100        }
101        if (closing && state != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
102            if (mHeadsetProfile != null) {
103                mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
104                mLocalDisconnect = true;
105            } else {
106                Log.e(TAG, "HFP disconnected but call update received, ignore.");
107            }
108            return;
109        }
110
111        Log.d(TAG, "Got call state change to " + state);
112        switch (state) {
113            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
114                setActive();
115                if (conference != null) {
116                    conference.setActive();
117                }
118                break;
119            case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
120            case BluetoothHeadsetClientCall.CALL_STATE_HELD:
121                setOnHold();
122                if (conference != null) {
123                    conference.setOnHold();
124                }
125                break;
126            case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
127            case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
128                setDialing();
129                break;
130            case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
131            case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
132                setRinging();
133                break;
134            case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
135                // TODO Use more specific causes
136                close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
137                break;
138            default:
139                Log.wtf(TAG, "Unexpected phone state " + state);
140        }
141        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
142                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
143                (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
144    }
145
146    private void finishInitializing() {
147        if (mCurrentCall == null) {
148            String number = getAddress().getSchemeSpecificPart();
149            Log.d(TAG, "Dialing " + number);
150            mHeadsetProfile.dial(mDevice, number);
151            setDialing();
152            // We will change state dependent on broadcasts from BluetoothHeadsetClientCall.
153        } else {
154            handleCallChanged(mCurrentCall);
155        }
156    }
157
158    private void close(int cause) {
159        Log.d(TAG, "Closing " + mClosed);
160        if (mClosed) {
161            return;
162        }
163        setDisconnected(new DisconnectCause(cause));
164
165        mClosed = true;
166        mCurrentCall = null;
167
168        destroy();
169    }
170
171    @Override
172    public void onPlayDtmfTone(char c) {
173        Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
174        if (!mClosed && mHeadsetProfile != null) {
175            mHeadsetProfile.sendDTMF(mDevice, (byte) c);
176        }
177    }
178
179    @Override
180    public synchronized void onDisconnect() {
181        Log.d(TAG, "onDisconnect " + mCurrentCall);
182        mClosing = true;
183        if (!mClosed) {
184            // In this state we can close the call without problems.
185            if (mHeadsetProfile != null && mCurrentCall != null) {
186                mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
187                mLocalDisconnect = true;
188            } else if (mCurrentCall == null) {
189                Log.w(TAG, "Call disconnected but call handle not received.");
190            }
191        }
192    }
193
194    @Override
195    public void onAbort() {
196        Log.d(TAG, "onAbort " + mCurrentCall);
197        onDisconnect();
198    }
199
200    @Override
201    public void onHold() {
202        Log.d(TAG, "onHold " + mCurrentCall);
203        if (!mClosed && mHeadsetProfile != null) {
204            mHeadsetProfile.holdCall(mDevice);
205        }
206    }
207
208    @Override
209    public void onUnhold() {
210        if (getConnectionService().getAllConnections().size() > 1) {
211            Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
212            return;
213        }
214        Log.d(TAG, "onUnhold " + mCurrentCall);
215        if (!mClosed && mHeadsetProfile != null) {
216            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
217        }
218    }
219
220    @Override
221    public void onAnswer() {
222        Log.d(TAG, "onAnswer " + mCurrentCall);
223        if (!mClosed) {
224            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
225        }
226    }
227
228    @Override
229    public void onReject() {
230        Log.d(TAG, "onReject " + mCurrentCall);
231        if (!mClosed) {
232            mHeadsetProfile.rejectCall(mDevice);
233        }
234    }
235
236    @Override
237    public boolean equals(Object o) {
238        if (!(o instanceof HfpClientConnection)) {
239            return false;
240        }
241        Uri otherAddr = ((HfpClientConnection) o).getAddress();
242        return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
243    }
244
245    @Override
246    public int hashCode() {
247        return getAddress() == null ? 0 : getAddress().hashCode();
248    }
249
250    @Override
251    public String toString() {
252        return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
253                mCurrentCall + "}";
254    }
255}
256