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