HfpClientConnection.java revision b88c0d7495471f3986844e8c8b2aca7f7f2f4c8e
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 = true; 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). 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. 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 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 99 public UUID getUUID() { 100 return mCurrentCall.getUUID(); 101 } 102 103 public void onHfpDisconnected() { 104 mHeadsetProfile = null; 105 close(DisconnectCause.ERROR); 106 } 107 108 public void onAdded() { 109 mAdded = true; 110 } 111 112 public BluetoothHeadsetClientCall getCall() { 113 return mCurrentCall; 114 } 115 116 public boolean inConference() { 117 return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() && 118 getState() != Connection.STATE_DISCONNECTED; 119 } 120 121 public void enterPrivateMode() { 122 mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId()); 123 setActive(); 124 } 125 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 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 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"); 180 setDisconnected(new DisconnectCause(cause)); 181 182 mClosed = true; 183 mCurrentCall = null; 184 185 destroy(); 186 } 187 188 public synchronized boolean isClosing() { 189 return mClosing; 190 } 191 192 @Override 193 public synchronized void onPlayDtmfTone(char c) { 194 if (DBG) { 195 Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall); 196 } 197 if (!mClosed) { 198 mHeadsetProfile.sendDTMF(mDevice, (byte) c); 199 } 200 } 201 202 @Override 203 public synchronized void onDisconnect() { 204 if (DBG) { 205 Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed); 206 } 207 // The call is not closed so we should send a terminate here. 208 if (!mClosed) { 209 mHeadsetProfile.terminateCall(mDevice, mCurrentCall); 210 mLocalDisconnect = true; 211 mClosing = true; 212 } 213 } 214 215 @Override 216 public void onAbort() { 217 if (DBG) { 218 Log.d(TAG, "onAbort " + mCurrentCall); 219 } 220 onDisconnect(); 221 } 222 223 @Override 224 public synchronized void onHold() { 225 if (DBG) { 226 Log.d(TAG, "onHold " + mCurrentCall); 227 } 228 if (!mClosed) { 229 mHeadsetProfile.holdCall(mDevice); 230 } 231 } 232 233 @Override 234 public synchronized void onUnhold() { 235 if (getConnectionService().getAllConnections().size() > 1) { 236 Log.w(TAG, "Ignoring unhold; call hold on the foreground call"); 237 return; 238 } 239 if (DBG) { 240 Log.d(TAG, "onUnhold " + mCurrentCall); 241 } 242 if (!mClosed) { 243 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD); 244 } 245 } 246 247 @Override 248 public synchronized void onAnswer() { 249 if (DBG) { 250 Log.d(TAG, "onAnswer " + mCurrentCall); 251 } 252 if (!mClosed) { 253 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); 254 } 255 } 256 257 @Override 258 public synchronized void onReject() { 259 if (DBG) { 260 Log.d(TAG, "onReject " + mCurrentCall); 261 } 262 if (!mClosed) { 263 mHeadsetProfile.rejectCall(mDevice); 264 } 265 } 266 267 @Override 268 public boolean equals(Object o) { 269 if (!(o instanceof HfpClientConnection)) { 270 return false; 271 } 272 Uri otherAddr = ((HfpClientConnection) o).getAddress(); 273 return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress()); 274 } 275 276 @Override 277 public int hashCode() { 278 return getAddress() == null ? 0 : getAddress().hashCode(); 279 } 280 281 @Override 282 public String toString() { 283 return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," + 284 mCurrentCall + "}"; 285 } 286} 287