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.TelecomManager;
27 import android.util.Log;
28 
29 public class HfpClientConnection extends Connection {
30     private static final String TAG = "HfpClientConnection";
31 
32     private final Context mContext;
33     private final BluetoothDevice mDevice;
34 
35     private BluetoothHeadsetClient mHeadsetProfile;
36     private BluetoothHeadsetClientCall mCurrentCall;
37     private boolean mClosed;
38     private boolean mLocalDisconnect;
39     private boolean mClientHasEcc;
40     private boolean mAdded;
41 
HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client, BluetoothHeadsetClientCall call, Uri number)42     public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client,
43             BluetoothHeadsetClientCall call, Uri number) {
44         mDevice = device;
45         mContext = context;
46         mHeadsetProfile = client;
47         mCurrentCall = call;
48         if (mHeadsetProfile != null) {
49             mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
50         }
51         setAudioModeIsVoip(false);
52         setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
53         setInitialized();
54 
55         if (mHeadsetProfile != null) {
56             finishInitializing();
57         }
58     }
59 
onHfpConnected(BluetoothHeadsetClient client)60     public void onHfpConnected(BluetoothHeadsetClient client) {
61         mHeadsetProfile = client;
62         mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
63         finishInitializing();
64     }
65 
onHfpDisconnected()66     public void onHfpDisconnected() {
67         mHeadsetProfile = null;
68         close(DisconnectCause.ERROR);
69     }
70 
onAdded()71     public void onAdded() {
72         mAdded = true;
73     }
74 
getCall()75     public BluetoothHeadsetClientCall getCall() {
76         return mCurrentCall;
77     }
78 
inConference()79     public boolean inConference() {
80         return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
81                 getState() != Connection.STATE_DISCONNECTED;
82     }
83 
enterPrivateMode()84     public void enterPrivateMode() {
85         mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
86         setActive();
87     }
88 
handleCallChanged(BluetoothHeadsetClientCall call)89     public void handleCallChanged(BluetoothHeadsetClientCall call) {
90         HfpClientConference conference = (HfpClientConference) getConference();
91         mCurrentCall = call;
92 
93         int state = call.getState();
94         Log.d(TAG, "Got call state change to " + state);
95         switch (state) {
96             case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
97                 setActive();
98                 if (conference != null) {
99                     conference.setActive();
100                 }
101                 break;
102             case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
103             case BluetoothHeadsetClientCall.CALL_STATE_HELD:
104                 setOnHold();
105                 if (conference != null) {
106                     conference.setOnHold();
107                 }
108                 break;
109             case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
110             case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
111                 setDialing();
112                 break;
113             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
114             case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
115                 setRinging();
116                 break;
117             case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
118                 // TODO Use more specific causes
119                 close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
120                 break;
121             default:
122                 Log.wtf(TAG, "Unexpected phone state " + state);
123         }
124         setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
125                 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
126                 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
127     }
128 
finishInitializing()129     private void finishInitializing() {
130         if (mCurrentCall == null) {
131             String number = getAddress().getSchemeSpecificPart();
132             Log.d(TAG, "Dialing " + number);
133             mHeadsetProfile.dial(mDevice, number);
134             setDialing();
135             // We will change state dependent on broadcasts from BluetoothHeadsetClientCall.
136         } else {
137             handleCallChanged(mCurrentCall);
138         }
139     }
140 
close(int cause)141     private void close(int cause) {
142         Log.d(TAG, "Closing " + mClosed);
143         if (mClosed) {
144             return;
145         }
146         setDisconnected(new DisconnectCause(cause));
147 
148         mClosed = true;
149         mCurrentCall = null;
150 
151         destroy();
152     }
153 
154     @Override
onPlayDtmfTone(char c)155     public void onPlayDtmfTone(char c) {
156         Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
157         if (!mClosed && mHeadsetProfile != null) {
158             mHeadsetProfile.sendDTMF(mDevice, (byte) c);
159         }
160     }
161 
162     @Override
onDisconnect()163     public void onDisconnect() {
164         Log.d(TAG, "onDisconnect " + mCurrentCall);
165         if (!mClosed) {
166             if (mHeadsetProfile != null && mCurrentCall != null) {
167                 mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
168                 mLocalDisconnect = true;
169             } else {
170                 close(DisconnectCause.LOCAL);
171             }
172         }
173     }
174 
175     @Override
onAbort()176     public void onAbort() {
177         Log.d(TAG, "onAbort " + mCurrentCall);
178         onDisconnect();
179     }
180 
181     @Override
onHold()182     public void onHold() {
183         Log.d(TAG, "onHold " + mCurrentCall);
184         if (!mClosed && mHeadsetProfile != null) {
185             mHeadsetProfile.holdCall(mDevice);
186         }
187     }
188 
189     @Override
onUnhold()190     public void onUnhold() {
191         if (getConnectionService().getAllConnections().size() > 1) {
192             Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
193             return;
194         }
195         Log.d(TAG, "onUnhold " + mCurrentCall);
196         if (!mClosed && mHeadsetProfile != null) {
197             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
198         }
199     }
200 
201     @Override
onAnswer()202     public void onAnswer() {
203         Log.d(TAG, "onAnswer " + mCurrentCall);
204         if (!mClosed) {
205             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
206         }
207     }
208 
209     @Override
onReject()210     public void onReject() {
211         Log.d(TAG, "onReject " + mCurrentCall);
212         if (!mClosed) {
213             mHeadsetProfile.rejectCall(mDevice);
214         }
215     }
216 
217     @Override
equals(Object o)218     public boolean equals(Object o) {
219         if (!(o instanceof HfpClientConnection)) {
220             return false;
221         }
222         Uri otherAddr = ((HfpClientConnection) o).getAddress();
223         return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
224     }
225 
226     @Override
hashCode()227     public int hashCode() {
228         return getAddress() == null ? 0 : getAddress().hashCode();
229     }
230 
231     @Override
toString()232     public String toString() {
233         return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
234                 mCurrentCall + "}";
235     }
236 }
237