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