HfpClientConnection.java revision 57a102e5548a7a395bcd7feec3c1df6d61be8187
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.TelecomManager; 27import android.util.Log; 28 29public class HfpClientConnection extends Connection { 30 private static final String TAG = "HfpClientConnection"; 31 32 private final Context mContext; 33 private final BluetoothDevice mDevice; 34 35 private BluetoothHeadsetClient mHeadsetProfile; 36 private BluetoothHeadsetClientCall mCurrentCall; 37 private boolean mClosing; 38 private boolean mClosed; 39 private boolean mLocalDisconnect; 40 private boolean mClientHasEcc; 41 private boolean mAdded; 42 43 public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client, 44 BluetoothHeadsetClientCall call, Uri number) { 45 mDevice = device; 46 mContext = context; 47 mHeadsetProfile = client; 48 mCurrentCall = call; 49 if (mHeadsetProfile != null) { 50 mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice); 51 } 52 setAudioModeIsVoip(false); 53 setAddress(number, TelecomManager.PRESENTATION_ALLOWED); 54 setInitialized(); 55 56 if (mHeadsetProfile != null) { 57 finishInitializing(); 58 } 59 } 60 61 public void onHfpConnected(BluetoothHeadsetClient client) { 62 mHeadsetProfile = client; 63 mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice); 64 finishInitializing(); 65 } 66 67 public void onHfpDisconnected() { 68 mHeadsetProfile = null; 69 close(DisconnectCause.ERROR); 70 } 71 72 public void onAdded() { 73 mAdded = true; 74 } 75 76 public BluetoothHeadsetClientCall getCall() { 77 return mCurrentCall; 78 } 79 80 public boolean inConference() { 81 return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() && 82 getState() != Connection.STATE_DISCONNECTED; 83 } 84 85 public void enterPrivateMode() { 86 mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId()); 87 setActive(); 88 } 89 90 public void handleCallChanged(BluetoothHeadsetClientCall call) { 91 HfpClientConference conference = (HfpClientConference) getConference(); 92 mCurrentCall = call; 93 int state = call.getState(); 94 95 // If this call is already terminated (locally) but we are only hearing about the handle of 96 // the call right now -- then close the call. 97 boolean closing = false; 98 synchronized (this) { 99 closing = mClosing; 100 } 101 if (closing && state != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) { 102 if (mHeadsetProfile != null) { 103 mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0); 104 mLocalDisconnect = true; 105 } else { 106 Log.e(TAG, "HFP disconnected but call update received, ignore."); 107 } 108 return; 109 } 110 111 Log.d(TAG, "Got call state change to " + state); 112 switch (state) { 113 case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE: 114 setActive(); 115 if (conference != null) { 116 conference.setActive(); 117 } 118 break; 119 case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD: 120 case BluetoothHeadsetClientCall.CALL_STATE_HELD: 121 setOnHold(); 122 if (conference != null) { 123 conference.setOnHold(); 124 } 125 break; 126 case BluetoothHeadsetClientCall.CALL_STATE_DIALING: 127 case BluetoothHeadsetClientCall.CALL_STATE_ALERTING: 128 setDialing(); 129 break; 130 case BluetoothHeadsetClientCall.CALL_STATE_INCOMING: 131 case BluetoothHeadsetClientCall.CALL_STATE_WAITING: 132 setRinging(); 133 break; 134 case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED: 135 // TODO Use more specific causes 136 close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE); 137 break; 138 default: 139 Log.wtf(TAG, "Unexpected phone state " + state); 140 } 141 setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE | 142 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE | 143 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0)); 144 } 145 146 private void finishInitializing() { 147 if (mCurrentCall == null) { 148 String number = getAddress().getSchemeSpecificPart(); 149 Log.d(TAG, "Dialing " + number); 150 mHeadsetProfile.dial(mDevice, number); 151 setDialing(); 152 // We will change state dependent on broadcasts from BluetoothHeadsetClientCall. 153 } else { 154 handleCallChanged(mCurrentCall); 155 } 156 } 157 158 private void close(int cause) { 159 Log.d(TAG, "Closing " + mClosed); 160 if (mClosed) { 161 return; 162 } 163 setDisconnected(new DisconnectCause(cause)); 164 165 mClosed = true; 166 mCurrentCall = null; 167 168 destroy(); 169 } 170 171 @Override 172 public void onPlayDtmfTone(char c) { 173 Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall); 174 if (!mClosed && mHeadsetProfile != null) { 175 mHeadsetProfile.sendDTMF(mDevice, (byte) c); 176 } 177 } 178 179 @Override 180 public synchronized void onDisconnect() { 181 Log.d(TAG, "onDisconnect " + mCurrentCall); 182 mClosing = true; 183 if (!mClosed) { 184 // In this state we can close the call without problems. 185 if (mHeadsetProfile != null && mCurrentCall != null) { 186 mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0); 187 mLocalDisconnect = true; 188 } else if (mCurrentCall == null) { 189 Log.w(TAG, "Call disconnected but call handle not received."); 190 } 191 } 192 } 193 194 @Override 195 public void onAbort() { 196 Log.d(TAG, "onAbort " + mCurrentCall); 197 onDisconnect(); 198 } 199 200 @Override 201 public void onHold() { 202 Log.d(TAG, "onHold " + mCurrentCall); 203 if (!mClosed && mHeadsetProfile != null) { 204 mHeadsetProfile.holdCall(mDevice); 205 } 206 } 207 208 @Override 209 public void onUnhold() { 210 if (getConnectionService().getAllConnections().size() > 1) { 211 Log.w(TAG, "Ignoring unhold; call hold on the foreground call"); 212 return; 213 } 214 Log.d(TAG, "onUnhold " + mCurrentCall); 215 if (!mClosed && mHeadsetProfile != null) { 216 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD); 217 } 218 } 219 220 @Override 221 public void onAnswer() { 222 Log.d(TAG, "onAnswer " + mCurrentCall); 223 if (!mClosed) { 224 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); 225 } 226 } 227 228 @Override 229 public void onReject() { 230 Log.d(TAG, "onReject " + mCurrentCall); 231 if (!mClosed) { 232 mHeadsetProfile.rejectCall(mDevice); 233 } 234 } 235 236 @Override 237 public boolean equals(Object o) { 238 if (!(o instanceof HfpClientConnection)) { 239 return false; 240 } 241 Uri otherAddr = ((HfpClientConnection) o).getAddress(); 242 return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress()); 243 } 244 245 @Override 246 public int hashCode() { 247 return getAddress() == null ? 0 : getAddress().hashCode(); 248 } 249 250 @Override 251 public String toString() { 252 return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," + 253 mCurrentCall + "}"; 254 } 255} 256