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 */ 16package com.android.bluetooth.hfpclient.connserv; 17 18import android.bluetooth.BluetoothDevice; 19import android.bluetooth.BluetoothHeadsetClient; 20import android.bluetooth.BluetoothHeadsetClientCall; 21import android.bluetooth.BluetoothProfile; 22import android.content.Context; 23import android.net.Uri; 24import android.telecom.Connection; 25import android.telecom.DisconnectCause; 26import android.telecom.PhoneAccount; 27import android.telecom.TelecomManager; 28import android.util.Log; 29 30import java.util.UUID; 31 32public class HfpClientConnection extends Connection { 33 private static final String TAG = "HfpClientConnection"; 34 private static final boolean DBG = false; 35 36 private final Context mContext; 37 private final BluetoothDevice mDevice; 38 private BluetoothHeadsetClient mHeadsetProfile; 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). 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. 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( 78 mDevice, number.getSchemeSpecificPart()); 79 if (mCurrentCall == null) { 80 close(DisconnectCause.ERROR); 81 Log.e(TAG, "Failed to create the call, dial failed."); 82 return; 83 } 84 85 mHeadsetProfile.connectAudio(device); 86 setInitializing(); 87 setDialing(); 88 finishInitializing(); 89 } 90 91 void finishInitializing() { 92 mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice); 93 setAudioModeIsVoip(false); 94 Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, mCurrentCall.getNumber(), null); 95 setAddress(number, TelecomManager.PRESENTATION_ALLOWED); 96 setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE | 97 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE | 98 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0)); 99 } 100 101 public UUID getUUID() { 102 return mCurrentCall.getUUID(); 103 } 104 105 public void onHfpDisconnected() { 106 mHeadsetProfile = null; 107 close(DisconnectCause.ERROR); 108 } 109 110 public void onAdded() { 111 mAdded = true; 112 } 113 114 public BluetoothHeadsetClientCall getCall() { 115 return mCurrentCall; 116 } 117 118 public boolean inConference() { 119 return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() && 120 getState() != Connection.STATE_DISCONNECTED; 121 } 122 123 public void enterPrivateMode() { 124 mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId()); 125 setActive(); 126 } 127 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 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 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 197 public synchronized boolean isClosing() { 198 return mClosing; 199 } 200 201 public synchronized BluetoothDevice getDevice() { 202 return mDevice; 203 } 204 205 @Override 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 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 229 public void onAbort() { 230 if (DBG) { 231 Log.d(TAG, "onAbort " + mCurrentCall); 232 } 233 onDisconnect(); 234 } 235 236 @Override 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 247 public synchronized void onUnhold() { 248 if (getConnectionService().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 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 mHeadsetProfile.connectAudio(mDevice); 269 } 270 271 @Override 272 public synchronized void onReject() { 273 if (DBG) { 274 Log.d(TAG, "onReject " + mCurrentCall); 275 } 276 if (!mClosed) { 277 mHeadsetProfile.rejectCall(mDevice); 278 } 279 } 280 281 @Override 282 public boolean equals(Object o) { 283 if (!(o instanceof HfpClientConnection)) { 284 return false; 285 } 286 Uri otherAddr = ((HfpClientConnection) o).getAddress(); 287 return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress()); 288 } 289 290 @Override 291 public String toString() { 292 return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," + 293 mCurrentCall + "}"; 294 } 295} 296