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