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