HfpClientConnection.java revision b88c0d7495471f3986844e8c8b2aca7f7f2f4c8e
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.PhoneAccount;
27import android.telecom.TelecomManager;
28import android.util.Log;
29
30import java.util.UUID;
31
32public class HfpClientConnection extends Connection {
33    private static final String TAG = "HfpClientConnection";
34    private static final boolean DBG = true;
35
36    private final Context mContext;
37    private final BluetoothDevice mDevice;
38    private BluetoothHeadsetClient mHeadsetProfile;
39
40    private BluetoothHeadsetClientCall mCurrentCall;
41    private boolean mClosed;
42    private boolean mClosing = false;
43    private boolean mLocalDisconnect;
44    private boolean mClientHasEcc;
45    private boolean mAdded;
46
47    // Constructor to be used when there's an existing call (such as that created on the AG or
48    // when connection happens and we see calls for the first time).
49    public HfpClientConnection(Context context, BluetoothDevice device,
50            BluetoothHeadsetClient client, BluetoothHeadsetClientCall call) {
51        mDevice = device;
52        mContext = context;
53        mHeadsetProfile = client;
54
55        if (call == null) {
56            throw new IllegalStateException("Call is null");
57        }
58
59        mCurrentCall = call;
60        handleCallChanged();
61        finishInitializing();
62    }
63
64    // Constructor to be used when a call is intiated on the HF. The call handle is obtained by
65    // using the dial() command.
66    public HfpClientConnection(Context context, BluetoothDevice device,
67            BluetoothHeadsetClient client, Uri number) {
68        mDevice = device;
69        mContext = context;
70        mHeadsetProfile = client;
71
72        if (mHeadsetProfile == null) {
73            throw new IllegalStateException("HeadsetProfile is null, returning");
74        }
75
76        mCurrentCall = mHeadsetProfile.dial(
77            mDevice, number.getSchemeSpecificPart());
78        if (mCurrentCall == null) {
79            close(DisconnectCause.ERROR);
80            Log.e(TAG, "Failed to create the call, dial failed.");
81            return;
82        }
83
84        setInitializing();
85        setDialing();
86        finishInitializing();
87    }
88
89    void finishInitializing() {
90        mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
91        setAudioModeIsVoip(false);
92        Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, mCurrentCall.getNumber(), null);
93        setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
94        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
95                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
96                (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
97    }
98
99    public UUID getUUID() {
100        return mCurrentCall.getUUID();
101    }
102
103    public void onHfpDisconnected() {
104        mHeadsetProfile = null;
105        close(DisconnectCause.ERROR);
106    }
107
108    public void onAdded() {
109        mAdded = true;
110    }
111
112    public BluetoothHeadsetClientCall getCall() {
113        return mCurrentCall;
114    }
115
116    public boolean inConference() {
117        return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
118                getState() != Connection.STATE_DISCONNECTED;
119    }
120
121    public void enterPrivateMode() {
122        mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
123        setActive();
124    }
125
126    public void updateCall(BluetoothHeadsetClientCall call) {
127        if (call == null) {
128            Log.e(TAG, "Updating call to a null value.");
129            return;
130        }
131        mCurrentCall = call;
132    }
133
134    public void handleCallChanged() {
135        HfpClientConference conference = (HfpClientConference) getConference();
136        int state = mCurrentCall.getState();
137
138        if (DBG) {
139            Log.d(TAG, "Got call state change to " + state);
140        }
141        switch (state) {
142            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
143                setActive();
144                if (conference != null) {
145                    conference.setActive();
146                }
147                break;
148            case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
149            case BluetoothHeadsetClientCall.CALL_STATE_HELD:
150                setOnHold();
151                if (conference != null) {
152                    conference.setOnHold();
153                }
154                break;
155            case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
156            case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
157                setDialing();
158                break;
159            case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
160            case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
161                setRinging();
162                break;
163            case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
164                // TODO Use more specific causes
165                close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
166                break;
167            default:
168                Log.wtf(TAG, "Unexpected phone state " + state);
169        }
170    }
171
172    public synchronized void close(int cause) {
173        if (DBG) {
174            Log.d(TAG, "Closing call " + mCurrentCall + "state: " + mClosed);
175        }
176        if (mClosed) {
177            return;
178        }
179        Log.d(TAG, "Setting " + mCurrentCall + " to disconnected");
180        setDisconnected(new DisconnectCause(cause));
181
182        mClosed = true;
183        mCurrentCall = null;
184
185        destroy();
186    }
187
188    public synchronized boolean isClosing() {
189        return mClosing;
190    }
191
192    @Override
193    public synchronized void onPlayDtmfTone(char c) {
194        if (DBG) {
195            Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
196        }
197        if (!mClosed) {
198            mHeadsetProfile.sendDTMF(mDevice, (byte) c);
199        }
200    }
201
202    @Override
203    public synchronized void onDisconnect() {
204        if (DBG) {
205            Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed);
206        }
207        // The call is not closed so we should send a terminate here.
208        if (!mClosed) {
209            mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
210            mLocalDisconnect = true;
211            mClosing = true;
212        }
213    }
214
215    @Override
216    public void onAbort() {
217        if (DBG) {
218            Log.d(TAG, "onAbort " + mCurrentCall);
219        }
220        onDisconnect();
221    }
222
223    @Override
224    public synchronized void onHold() {
225        if (DBG) {
226            Log.d(TAG, "onHold " + mCurrentCall);
227        }
228        if (!mClosed) {
229            mHeadsetProfile.holdCall(mDevice);
230        }
231    }
232
233    @Override
234    public synchronized void onUnhold() {
235        if (getConnectionService().getAllConnections().size() > 1) {
236            Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
237            return;
238        }
239        if (DBG) {
240            Log.d(TAG, "onUnhold " + mCurrentCall);
241        }
242        if (!mClosed) {
243            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
244        }
245    }
246
247    @Override
248    public synchronized void onAnswer() {
249        if (DBG) {
250            Log.d(TAG, "onAnswer " + mCurrentCall);
251        }
252        if (!mClosed) {
253            mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
254        }
255    }
256
257    @Override
258    public synchronized void onReject() {
259        if (DBG) {
260            Log.d(TAG, "onReject " + mCurrentCall);
261        }
262        if (!mClosed) {
263            mHeadsetProfile.rejectCall(mDevice);
264        }
265    }
266
267    @Override
268    public boolean equals(Object o) {
269        if (!(o instanceof HfpClientConnection)) {
270            return false;
271        }
272        Uri otherAddr = ((HfpClientConnection) o).getAddress();
273        return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
274    }
275
276    @Override
277    public int hashCode() {
278        return getAddress() == null ? 0 : getAddress().hashCode();
279    }
280
281    @Override
282    public String toString() {
283        return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
284                mCurrentCall + "}";
285    }
286}
287