• 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.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 
39     private BluetoothHeadsetClientCall mCurrentCall;
40     private int mPreviousCallState = -1;
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(mDevice, number.getSchemeSpecificPart());
77         if (mCurrentCall == null) {
78             close(DisconnectCause.ERROR);
79             Log.e(TAG, "Failed to create the call, dial failed.");
80             return;
81         }
82 
83         setInitializing();
84         setDialing();
85         finishInitializing();
86     }
87 
finishInitializing()88     void finishInitializing() {
89         mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
90         setAudioModeIsVoip(false);
91         Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, mCurrentCall.getNumber(), null);
92         setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
93         setConnectionCapabilities(
94                 CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE | CAPABILITY_SEPARATE_FROM_CONFERENCE
95                         | CAPABILITY_DISCONNECT_FROM_CONFERENCE | (
96                         getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD
97                                 : 0));
98     }
99 
getUUID()100     public UUID getUUID() {
101         return mCurrentCall.getUUID();
102     }
103 
onHfpDisconnected()104     public void onHfpDisconnected() {
105         mHeadsetProfile = null;
106         close(DisconnectCause.ERROR);
107     }
108 
onAdded()109     public void onAdded() {
110         mAdded = true;
111     }
112 
getCall()113     public BluetoothHeadsetClientCall getCall() {
114         return mCurrentCall;
115     }
116 
inConference()117     public boolean inConference() {
118         return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty()
119                 && getState() != Connection.STATE_DISCONNECTED;
120     }
121 
enterPrivateMode()122     public void enterPrivateMode() {
123         mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
124         setActive();
125     }
126 
updateCall(BluetoothHeadsetClientCall call)127     public void updateCall(BluetoothHeadsetClientCall call) {
128         if (call == null) {
129             Log.e(TAG, "Updating call to a null value.");
130             return;
131         }
132         mCurrentCall = call;
133     }
134 
handleCallChanged()135     public void handleCallChanged() {
136         HfpClientConference conference = (HfpClientConference) getConference();
137         int state = mCurrentCall.getState();
138 
139         if (DBG) {
140             Log.d(TAG, "Got call state change to " + state);
141         }
142         switch (state) {
143             case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
144                 setActive();
145                 if (conference != null) {
146                     conference.setActive();
147                 }
148                 break;
149             case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
150             case BluetoothHeadsetClientCall.CALL_STATE_HELD:
151                 setOnHold();
152                 if (conference != null) {
153                     conference.setOnHold();
154                 }
155                 break;
156             case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
157             case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
158                 setDialing();
159                 break;
160             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
161             case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
162                 setRinging();
163                 break;
164             case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
165                 if (mPreviousCallState == BluetoothHeadsetClientCall.CALL_STATE_INCOMING
166                         || mPreviousCallState == BluetoothHeadsetClientCall.CALL_STATE_WAITING) {
167                     close(DisconnectCause.MISSED);
168                 } else if (mLocalDisconnect) {
169                     close(DisconnectCause.LOCAL);
170                 } else {
171                     close(DisconnectCause.REMOTE);
172                 }
173                 break;
174             default:
175                 Log.wtf(TAG, "Unexpected phone state " + state);
176         }
177         mPreviousCallState = state;
178     }
179 
close(int cause)180     public synchronized void close(int cause) {
181         if (DBG) {
182             Log.d(TAG, "Closing call " + mCurrentCall + "state: " + mClosed);
183         }
184         if (mClosed) {
185             return;
186         }
187         Log.d(TAG, "Setting " + mCurrentCall + " to disconnected " + getTelecomCallId());
188         setDisconnected(new DisconnectCause(cause));
189 
190         mClosed = true;
191         mCurrentCall = null;
192 
193         destroy();
194     }
195 
isClosing()196     public synchronized boolean isClosing() {
197         return mClosing;
198     }
199 
getDevice()200     public synchronized BluetoothDevice getDevice() {
201         return mDevice;
202     }
203 
204     @Override
onPlayDtmfTone(char c)205     public synchronized void onPlayDtmfTone(char c) {
206         if (DBG) {
207             Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
208         }
209         if (!mClosed) {
210             mHeadsetProfile.sendDTMF(mDevice, (byte) c);
211         }
212     }
213 
214     @Override
onDisconnect()215     public synchronized void onDisconnect() {
216         if (DBG) {
217             Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed);
218         }
219         // The call is not closed so we should send a terminate here.
220         if (!mClosed) {
221             mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
222             mLocalDisconnect = true;
223             mClosing = true;
224         }
225     }
226 
227     @Override
onAbort()228     public void onAbort() {
229         if (DBG) {
230             Log.d(TAG, "onAbort " + mCurrentCall);
231         }
232         onDisconnect();
233     }
234 
235     @Override
onHold()236     public synchronized void onHold() {
237         if (DBG) {
238             Log.d(TAG, "onHold " + mCurrentCall);
239         }
240         if (!mClosed) {
241             mHeadsetProfile.holdCall(mDevice);
242         }
243     }
244 
245     @Override
onUnhold()246     public synchronized void onUnhold() {
247         if (getConnectionService().getAllConnections().size() > 1) {
248             Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
249             return;
250         }
251         if (DBG) {
252             Log.d(TAG, "onUnhold " + mCurrentCall);
253         }
254         if (!mClosed) {
255             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
256         }
257     }
258 
259     @Override
onAnswer()260     public synchronized void onAnswer() {
261         if (DBG) {
262             Log.d(TAG, "onAnswer " + mCurrentCall);
263         }
264         if (!mClosed) {
265             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
266         }
267     }
268 
269     @Override
onReject()270     public synchronized void onReject() {
271         if (DBG) {
272             Log.d(TAG, "onReject " + mCurrentCall);
273         }
274         if (!mClosed) {
275             mHeadsetProfile.rejectCall(mDevice);
276         }
277     }
278 
279     @Override
equals(Object o)280     public boolean equals(Object o) {
281         if (!(o instanceof HfpClientConnection)) {
282             return false;
283         }
284         Uri otherAddr = ((HfpClientConnection) o).getAddress();
285         return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
286     }
287 
288     @Override
toString()289     public String toString() {
290         return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + ","
291                 + mCurrentCall + "}";
292     }
293 }
294