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