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.bluetooth.BluetoothProfile; 22 import android.content.Context; 23 import android.net.Uri; 24 import android.telecom.Connection; 25 import android.telecom.DisconnectCause; 26 import android.telecom.PhoneAccount; 27 import android.telecom.TelecomManager; 28 import android.util.Log; 29 30 import java.util.UUID; 31 32 public 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 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( 77 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(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE | 95 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE | 96 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0)); 97 } 98 getUUID()99 public UUID getUUID() { 100 return mCurrentCall.getUUID(); 101 } 102 onHfpDisconnected()103 public void onHfpDisconnected() { 104 mHeadsetProfile = null; 105 close(DisconnectCause.ERROR); 106 } 107 onAdded()108 public void onAdded() { 109 mAdded = true; 110 } 111 getCall()112 public BluetoothHeadsetClientCall getCall() { 113 return mCurrentCall; 114 } 115 inConference()116 public boolean inConference() { 117 return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() && 118 getState() != Connection.STATE_DISCONNECTED; 119 } 120 enterPrivateMode()121 public void enterPrivateMode() { 122 mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId()); 123 setActive(); 124 } 125 updateCall(BluetoothHeadsetClientCall call)126 public void updateCall(BluetoothHeadsetClientCall call) { 127 if (call == null) { 128 Log.e(TAG, "Updating call to a null value."); 129 return; 130 } 131 mCurrentCall = call; 132 } 133 handleCallChanged()134 public void handleCallChanged() { 135 HfpClientConference conference = (HfpClientConference) getConference(); 136 int state = mCurrentCall.getState(); 137 138 if (DBG) { 139 Log.d(TAG, "Got call state change to " + state); 140 } 141 switch (state) { 142 case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE: 143 setActive(); 144 if (conference != null) { 145 conference.setActive(); 146 } 147 break; 148 case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD: 149 case BluetoothHeadsetClientCall.CALL_STATE_HELD: 150 setOnHold(); 151 if (conference != null) { 152 conference.setOnHold(); 153 } 154 break; 155 case BluetoothHeadsetClientCall.CALL_STATE_DIALING: 156 case BluetoothHeadsetClientCall.CALL_STATE_ALERTING: 157 setDialing(); 158 break; 159 case BluetoothHeadsetClientCall.CALL_STATE_INCOMING: 160 case BluetoothHeadsetClientCall.CALL_STATE_WAITING: 161 setRinging(); 162 break; 163 case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED: 164 // TODO Use more specific causes 165 close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE); 166 break; 167 default: 168 Log.wtf(TAG, "Unexpected phone state " + state); 169 } 170 } 171 close(int cause)172 public synchronized void close(int cause) { 173 if (DBG) { 174 Log.d(TAG, "Closing call " + mCurrentCall + "state: " + mClosed); 175 } 176 if (mClosed) { 177 return; 178 } 179 Log.d(TAG, "Setting " + mCurrentCall + " to disconnected " + getTelecomCallId()); 180 setDisconnected(new DisconnectCause(cause)); 181 182 mClosed = true; 183 mCurrentCall = null; 184 185 destroy(); 186 } 187 isClosing()188 public synchronized boolean isClosing() { 189 return mClosing; 190 } 191 getDevice()192 public synchronized BluetoothDevice getDevice() { 193 return mDevice; 194 } 195 196 @Override onPlayDtmfTone(char c)197 public synchronized void onPlayDtmfTone(char c) { 198 if (DBG) { 199 Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall); 200 } 201 if (!mClosed) { 202 mHeadsetProfile.sendDTMF(mDevice, (byte) c); 203 } 204 } 205 206 @Override onDisconnect()207 public synchronized void onDisconnect() { 208 if (DBG) { 209 Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed); 210 } 211 // The call is not closed so we should send a terminate here. 212 if (!mClosed) { 213 mHeadsetProfile.terminateCall(mDevice, mCurrentCall); 214 mLocalDisconnect = true; 215 mClosing = true; 216 } 217 } 218 219 @Override onAbort()220 public void onAbort() { 221 if (DBG) { 222 Log.d(TAG, "onAbort " + mCurrentCall); 223 } 224 onDisconnect(); 225 } 226 227 @Override onHold()228 public synchronized void onHold() { 229 if (DBG) { 230 Log.d(TAG, "onHold " + mCurrentCall); 231 } 232 if (!mClosed) { 233 mHeadsetProfile.holdCall(mDevice); 234 } 235 } 236 237 @Override onUnhold()238 public synchronized void onUnhold() { 239 if (getConnectionService().getAllConnections().size() > 1) { 240 Log.w(TAG, "Ignoring unhold; call hold on the foreground call"); 241 return; 242 } 243 if (DBG) { 244 Log.d(TAG, "onUnhold " + mCurrentCall); 245 } 246 if (!mClosed) { 247 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD); 248 } 249 } 250 251 @Override onAnswer()252 public synchronized void onAnswer() { 253 if (DBG) { 254 Log.d(TAG, "onAnswer " + mCurrentCall); 255 } 256 if (!mClosed) { 257 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); 258 } 259 } 260 261 @Override onReject()262 public synchronized void onReject() { 263 if (DBG) { 264 Log.d(TAG, "onReject " + mCurrentCall); 265 } 266 if (!mClosed) { 267 mHeadsetProfile.rejectCall(mDevice); 268 } 269 } 270 271 @Override equals(Object o)272 public boolean equals(Object o) { 273 if (!(o instanceof HfpClientConnection)) { 274 return false; 275 } 276 Uri otherAddr = ((HfpClientConnection) o).getAddress(); 277 return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress()); 278 } 279 280 @Override toString()281 public String toString() { 282 return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," + 283 mCurrentCall + "}"; 284 } 285 } 286