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