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