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