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