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