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.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.UUID;
30 
31 public class HfpClientConnection extends Connection {
32     private static final String TAG = "HfpClientConnection";
33     private static final boolean DBG = false;
34 
35     private final Context mContext;
36     private final BluetoothDevice mDevice;
37     private BluetoothHeadsetClient mHeadsetProfile;
38     private HfpClientConnectionService mHfpClientConnectionService;
39 
40     private BluetoothHeadsetClientCall mCurrentCall;
41     private int mPreviousCallState = -1;
42     private boolean mClosed;
43     private boolean mClosing = false;
44     private boolean mLocalDisconnect;
45     private boolean mClientHasEcc;
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(Context context, BluetoothDevice device, BluetoothHeadsetClient client, BluetoothHeadsetClientCall call)50     public HfpClientConnection(Context context, BluetoothDevice device,
51             BluetoothHeadsetClient client, BluetoothHeadsetClientCall call) {
52         mDevice = device;
53         mContext = context;
54         mHeadsetProfile = client;
55 
56         if (call == null) {
57             throw new IllegalStateException("Call is null");
58         }
59 
60         mCurrentCall = call;
61         handleCallChanged();
62         finishInitializing();
63     }
64 
65     // Constructor to be used when a call is intiated on the HF. The call handle is obtained by
66     // using the dial() command.
HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client, Uri number)67     public HfpClientConnection(Context context, BluetoothDevice device,
68             BluetoothHeadsetClient client, Uri number) {
69         mDevice = device;
70         mContext = context;
71         mHeadsetProfile = client;
72 
73         if (mHeadsetProfile == null) {
74             throw new IllegalStateException("HeadsetProfile is null, returning");
75         }
76 
77         mCurrentCall = mHeadsetProfile.dial(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 
finishInitializing()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(
95                 CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE | CAPABILITY_SEPARATE_FROM_CONFERENCE
96                         | CAPABILITY_DISCONNECT_FROM_CONFERENCE | (
97                         getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD
98                                 : 0));
99     }
100 
getUUID()101     public UUID getUUID() {
102         return mCurrentCall.getUUID();
103     }
104 
onHfpDisconnected()105     public void onHfpDisconnected() {
106         mHeadsetProfile = null;
107         close(DisconnectCause.ERROR);
108     }
109 
onAdded()110     public void onAdded() {
111         mAdded = true;
112     }
113 
getCall()114     public BluetoothHeadsetClientCall getCall() {
115         return mCurrentCall;
116     }
117 
inConference()118     public boolean inConference() {
119         return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty()
120                 && getState() != Connection.STATE_DISCONNECTED;
121     }
122 
enterPrivateMode()123     public void enterPrivateMode() {
124         mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
125         setActive();
126     }
127 
updateCall(BluetoothHeadsetClientCall call)128     public void updateCall(BluetoothHeadsetClientCall call) {
129         if (call == null) {
130             Log.e(TAG, "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         if (DBG) {
141             Log.d(TAG, "Got call state change to " + state);
142         }
143         switch (state) {
144             case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
145                 setActive();
146                 if (conference != null) {
147                     conference.setActive();
148                 }
149                 break;
150             case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
151             case BluetoothHeadsetClientCall.CALL_STATE_HELD:
152                 setOnHold();
153                 if (conference != null) {
154                     conference.setOnHold();
155                 }
156                 break;
157             case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
158             case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
159                 setDialing();
160                 break;
161             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
162             case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
163                 setRinging();
164                 break;
165             case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
166                 if (mPreviousCallState == BluetoothHeadsetClientCall.CALL_STATE_INCOMING
167                         || mPreviousCallState == BluetoothHeadsetClientCall.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, "Unexpected phone state " + state);
177         }
178         mPreviousCallState = state;
179     }
180 
close(int cause)181     public synchronized void close(int cause) {
182         if (DBG) {
183             Log.d(TAG, "Closing call " + mCurrentCall + "state: " + mClosed);
184         }
185         if (mClosed) {
186             return;
187         }
188         Log.d(TAG, "Setting " + mCurrentCall + " to disconnected " + getTelecomCallId());
189         setDisconnected(new DisconnectCause(cause));
190 
191         mClosed = true;
192         mCurrentCall = null;
193 
194         destroy();
195     }
196 
isClosing()197     public synchronized boolean isClosing() {
198         return mClosing;
199     }
200 
getDevice()201     public synchronized BluetoothDevice getDevice() {
202         return mDevice;
203     }
204 
205     @Override
onPlayDtmfTone(char c)206     public synchronized void onPlayDtmfTone(char c) {
207         if (DBG) {
208             Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
209         }
210         if (!mClosed) {
211             mHeadsetProfile.sendDTMF(mDevice, (byte) c);
212         }
213     }
214 
215     @Override
onDisconnect()216     public synchronized void onDisconnect() {
217         if (DBG) {
218             Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed);
219         }
220         // The call is not closed so we should send a terminate here.
221         if (!mClosed) {
222             mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
223             mLocalDisconnect = true;
224             mClosing = true;
225         }
226     }
227 
228     @Override
onAbort()229     public void onAbort() {
230         if (DBG) {
231             Log.d(TAG, "onAbort " + mCurrentCall);
232         }
233         onDisconnect();
234     }
235 
236     @Override
onHold()237     public synchronized void onHold() {
238         if (DBG) {
239             Log.d(TAG, "onHold " + mCurrentCall);
240         }
241         if (!mClosed) {
242             mHeadsetProfile.holdCall(mDevice);
243         }
244     }
245 
246     @Override
onUnhold()247     public synchronized void onUnhold() {
248         if (getHfpClientConnectionService().getAllConnections().size() > 1) {
249             Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
250             return;
251         }
252         if (DBG) {
253             Log.d(TAG, "onUnhold " + mCurrentCall);
254         }
255         if (!mClosed) {
256             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
257         }
258     }
259 
260     @Override
onAnswer()261     public synchronized void onAnswer() {
262         if (DBG) {
263             Log.d(TAG, "onAnswer " + mCurrentCall);
264         }
265         if (!mClosed) {
266             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
267         }
268     }
269 
270     @Override
onReject()271     public synchronized void onReject() {
272         if (DBG) {
273             Log.d(TAG, "onReject " + mCurrentCall);
274         }
275         if (!mClosed) {
276             mHeadsetProfile.rejectCall(mDevice);
277         }
278     }
279 
280     @Override
equals(Object o)281     public boolean equals(Object o) {
282         if (!(o instanceof HfpClientConnection)) {
283             return false;
284         }
285         Uri otherAddr = ((HfpClientConnection) o).getAddress();
286         return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
287     }
288 
289     @Override
toString()290     public String toString() {
291         return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + ","
292                 + mCurrentCall + "}";
293     }
294 
setHfpClientConnectionService( HfpClientConnectionService hfpClientConnectionService)295     public void setHfpClientConnectionService(
296             HfpClientConnectionService hfpClientConnectionService) {
297         mHfpClientConnectionService = hfpClientConnectionService;
298     }
299 
getHfpClientConnectionService()300     public HfpClientConnectionService getHfpClientConnectionService() {
301         return mHfpClientConnectionService;
302     }
303 
304 }
305