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