• 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.bluetooth.BluetoothProfile;
22 import android.content.Context;
23 import android.net.Uri;
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 final Context mContext;
37     private final BluetoothDevice mDevice;
38     private BluetoothHeadsetClient mHeadsetProfile;
39 
40     private BluetoothHeadsetClientCall mCurrentCall;
41     private boolean mClosed;
42     private boolean mClosing = false;
43     private boolean mLocalDisconnect;
44     private boolean mClientHasEcc;
45     private boolean mAdded;
46 
47     // Constructor to be used when there's an existing call (such as that created on the AG or
48     // when connection happens and we see calls for the first time).
HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client, BluetoothHeadsetClientCall call)49     public HfpClientConnection(Context context, BluetoothDevice device,
50             BluetoothHeadsetClient client, BluetoothHeadsetClientCall call) {
51         mDevice = device;
52         mContext = context;
53         mHeadsetProfile = client;
54 
55         if (call == null) {
56             throw new IllegalStateException("Call is null");
57         }
58 
59         mCurrentCall = call;
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(Context context, BluetoothDevice device, BluetoothHeadsetClient client, Uri number)66     public HfpClientConnection(Context context, BluetoothDevice device,
67             BluetoothHeadsetClient client, Uri number) {
68         mDevice = device;
69         mContext = context;
70         mHeadsetProfile = client;
71 
72         if (mHeadsetProfile == null) {
73             throw new IllegalStateException("HeadsetProfile is null, returning");
74         }
75 
76         mCurrentCall = mHeadsetProfile.dial(
77             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(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
95                 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
96                 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
97     }
98 
getUUID()99     public UUID getUUID() {
100         return mCurrentCall.getUUID();
101     }
102 
onHfpDisconnected()103     public void onHfpDisconnected() {
104         mHeadsetProfile = null;
105         close(DisconnectCause.ERROR);
106     }
107 
onAdded()108     public void onAdded() {
109         mAdded = true;
110     }
111 
getCall()112     public BluetoothHeadsetClientCall getCall() {
113         return mCurrentCall;
114     }
115 
inConference()116     public boolean inConference() {
117         return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
118                 getState() != Connection.STATE_DISCONNECTED;
119     }
120 
enterPrivateMode()121     public void enterPrivateMode() {
122         mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
123         setActive();
124     }
125 
updateCall(BluetoothHeadsetClientCall call)126     public void updateCall(BluetoothHeadsetClientCall call) {
127         if (call == null) {
128             Log.e(TAG, "Updating call to a null value.");
129             return;
130         }
131         mCurrentCall = call;
132     }
133 
handleCallChanged()134     public void handleCallChanged() {
135         HfpClientConference conference = (HfpClientConference) getConference();
136         int state = mCurrentCall.getState();
137 
138         if (DBG) {
139             Log.d(TAG, "Got call state change to " + state);
140         }
141         switch (state) {
142             case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
143                 setActive();
144                 if (conference != null) {
145                     conference.setActive();
146                 }
147                 break;
148             case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
149             case BluetoothHeadsetClientCall.CALL_STATE_HELD:
150                 setOnHold();
151                 if (conference != null) {
152                     conference.setOnHold();
153                 }
154                 break;
155             case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
156             case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
157                 setDialing();
158                 break;
159             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
160             case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
161                 setRinging();
162                 break;
163             case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
164                 // TODO Use more specific causes
165                 close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
166                 break;
167             default:
168                 Log.wtf(TAG, "Unexpected phone state " + state);
169         }
170     }
171 
close(int cause)172     public synchronized void close(int cause) {
173         if (DBG) {
174             Log.d(TAG, "Closing call " + mCurrentCall + "state: " + mClosed);
175         }
176         if (mClosed) {
177             return;
178         }
179         Log.d(TAG, "Setting " + mCurrentCall + " to disconnected " + getTelecomCallId());
180         setDisconnected(new DisconnectCause(cause));
181 
182         mClosed = true;
183         mCurrentCall = null;
184 
185         destroy();
186     }
187 
isClosing()188     public synchronized boolean isClosing() {
189         return mClosing;
190     }
191 
getDevice()192     public synchronized BluetoothDevice getDevice() {
193         return mDevice;
194     }
195 
196     @Override
onPlayDtmfTone(char c)197     public synchronized void onPlayDtmfTone(char c) {
198         if (DBG) {
199             Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
200         }
201         if (!mClosed) {
202             mHeadsetProfile.sendDTMF(mDevice, (byte) c);
203         }
204     }
205 
206     @Override
onDisconnect()207     public synchronized void onDisconnect() {
208         if (DBG) {
209             Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed);
210         }
211         // The call is not closed so we should send a terminate here.
212         if (!mClosed) {
213             mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
214             mLocalDisconnect = true;
215             mClosing = true;
216         }
217     }
218 
219     @Override
onAbort()220     public void onAbort() {
221         if (DBG) {
222             Log.d(TAG, "onAbort " + mCurrentCall);
223         }
224         onDisconnect();
225     }
226 
227     @Override
onHold()228     public synchronized void onHold() {
229         if (DBG) {
230             Log.d(TAG, "onHold " + mCurrentCall);
231         }
232         if (!mClosed) {
233             mHeadsetProfile.holdCall(mDevice);
234         }
235     }
236 
237     @Override
onUnhold()238     public synchronized void onUnhold() {
239         if (getConnectionService().getAllConnections().size() > 1) {
240             Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
241             return;
242         }
243         if (DBG) {
244             Log.d(TAG, "onUnhold " + mCurrentCall);
245         }
246         if (!mClosed) {
247             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
248         }
249     }
250 
251     @Override
onAnswer()252     public synchronized void onAnswer() {
253         if (DBG) {
254             Log.d(TAG, "onAnswer " + mCurrentCall);
255         }
256         if (!mClosed) {
257             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
258         }
259     }
260 
261     @Override
onReject()262     public synchronized void onReject() {
263         if (DBG) {
264             Log.d(TAG, "onReject " + mCurrentCall);
265         }
266         if (!mClosed) {
267             mHeadsetProfile.rejectCall(mDevice);
268         }
269     }
270 
271     @Override
equals(Object o)272     public boolean equals(Object o) {
273         if (!(o instanceof HfpClientConnection)) {
274             return false;
275         }
276         Uri otherAddr = ((HfpClientConnection) o).getAddress();
277         return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
278     }
279 
280     @Override
toString()281     public String toString() {
282         return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
283                 mCurrentCall + "}";
284     }
285 }
286