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.BluetoothAdapter; 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothHeadsetClient; 21 import android.bluetooth.BluetoothHeadsetClientCall; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.telecom.Connection; 31 import android.telecom.ConnectionRequest; 32 import android.telecom.ConnectionService; 33 import android.telecom.PhoneAccount; 34 import android.telecom.PhoneAccountHandle; 35 import android.telecom.TelecomManager; 36 import android.util.Log; 37 38 import com.android.bluetooth.hfpclient.HeadsetClientService; 39 40 import java.util.Arrays; 41 import java.util.HashMap; 42 import java.util.Iterator; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Objects; 46 47 public class HfpClientConnectionService extends ConnectionService { 48 private static final String TAG = "HfpClientConnService"; 49 private static final boolean DBG = true; 50 51 public static final String HFP_SCHEME = "hfpc"; 52 53 private BluetoothAdapter mAdapter; 54 55 // BluetoothHeadset proxy. 56 private BluetoothHeadsetClient mHeadsetProfile; 57 private TelecomManager mTelecomManager; 58 59 private final Map<BluetoothDevice, HfpClientDeviceBlock> mDeviceBlocks = new HashMap<>(); 60 61 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 62 @Override 63 public void onReceive(Context context, Intent intent) { 64 if (DBG) { 65 Log.d(TAG, "onReceive " + intent); 66 } 67 String action = intent != null ? intent.getAction() : null; 68 69 if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 70 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 71 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 72 73 if (newState == BluetoothProfile.STATE_CONNECTED) { 74 if (DBG) { 75 Log.d(TAG, "Established connection with " + device); 76 } 77 78 HfpClientDeviceBlock block = null; 79 if ((block = createBlockForDevice(device)) == null) { 80 Log.w(TAG, "Block already exists for device " + device + " ignoring."); 81 } 82 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 83 if (DBG) { 84 Log.d(TAG, "Disconnecting from " + device); 85 } 86 87 // Disconnect any inflight calls from the connection service. 88 synchronized (HfpClientConnectionService.this) { 89 HfpClientDeviceBlock block = mDeviceBlocks.remove(device); 90 if (block == null) { 91 Log.w(TAG, "Disconnect for device but no block " + device); 92 return; 93 } 94 block.cleanup(); 95 // Block should be subsequently garbage collected 96 block = null; 97 } 98 } 99 } else if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) { 100 BluetoothHeadsetClientCall call = 101 intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL); 102 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 103 HfpClientDeviceBlock block = findBlockForDevice(call.getDevice()); 104 if (block == null) { 105 Log.w(TAG, "Call changed but no block for device " + device); 106 return; 107 } 108 109 // If we are not connected, then when we actually do get connected -- 110 // the calls should 111 // be added (see ACTION_CONNECTION_STATE_CHANGED intent above). 112 block.handleCall(call); 113 } 114 } 115 }; 116 117 @Override onCreate()118 public void onCreate() { 119 super.onCreate(); 120 if (DBG) { 121 Log.d(TAG, "onCreate"); 122 } 123 mAdapter = BluetoothAdapter.getDefaultAdapter(); 124 mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 125 if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts(); 126 mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT); 127 } 128 129 @Override onDestroy()130 public void onDestroy() { 131 if (DBG) { 132 Log.d(TAG, "onDestroy called"); 133 } 134 // Close the profile. 135 if (mHeadsetProfile != null) { 136 mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile); 137 } 138 139 // Unregister the broadcast receiver. 140 try { 141 unregisterReceiver(mBroadcastReceiver); 142 } catch (IllegalArgumentException ex) { 143 Log.w(TAG, "Receiver was not registered."); 144 } 145 146 // Unregister the phone account. This should ideally happen when disconnection ensues but in 147 // case the service crashes we may need to force clean. 148 disconnectAll(); 149 } 150 disconnectAll()151 private synchronized void disconnectAll() { 152 for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it = 153 mDeviceBlocks.entrySet().iterator(); it.hasNext(); ) { 154 it.next().getValue().cleanup(); 155 it.remove(); 156 } 157 } 158 159 @Override onStartCommand(Intent intent, int flags, int startId)160 public int onStartCommand(Intent intent, int flags, int startId) { 161 if (DBG) { 162 Log.d(TAG, "onStartCommand " + intent); 163 } 164 // In order to make sure that the service is sticky (recovers from errors when HFP 165 // connection is still active) and to stop it we need a special intent since stopService 166 // only recreates it. 167 if (intent != null && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, 168 false)) { 169 // Stop the service. 170 stopSelf(); 171 return 0; 172 } else { 173 IntentFilter filter = new IntentFilter(); 174 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 175 filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED); 176 registerReceiver(mBroadcastReceiver, filter); 177 return START_STICKY; 178 } 179 } 180 181 // This method is called whenever there is a new incoming call (or right after BT connection). 182 @Override onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)183 public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount, 184 ConnectionRequest request) { 185 if (DBG) { 186 Log.d(TAG, 187 "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request); 188 } 189 190 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 191 if (block == null) { 192 Log.w(TAG, "HfpClient does not support having a connection manager"); 193 return null; 194 } 195 196 // We should already have a connection by this time. 197 BluetoothHeadsetClientCall call = 198 request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 199 HfpClientConnection connection = block.onCreateIncomingConnection(call); 200 connection.setHfpClientConnectionService(this); 201 return connection; 202 } 203 204 // This method is called *only if* Dialer UI is used to place an outgoing call. 205 @Override onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)206 public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount, 207 ConnectionRequest request) { 208 if (DBG) { 209 Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount); 210 } 211 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 212 if (block == null) { 213 Log.w(TAG, "HfpClient does not support having a connection manager"); 214 return null; 215 } 216 HfpClientConnection connection = block.onCreateOutgoingConnection(request.getAddress()); 217 connection.setHfpClientConnectionService(this); 218 return connection; 219 } 220 221 // This method is called when: 222 // 1. Outgoing call created from the AG. 223 // 2. Call transfer from AG -> HF (on connection when existed call present). 224 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)225 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount, 226 ConnectionRequest request) { 227 if (DBG) { 228 Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount); 229 } 230 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 231 if (block == null) { 232 Log.w(TAG, "HfpClient does not support having a connection manager"); 233 return null; 234 } 235 236 // We should already have a connection by this time. 237 BluetoothHeadsetClientCall call = 238 request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 239 HfpClientConnection connection = block.onCreateUnknownConnection(call); 240 connection.setHfpClientConnectionService(this); 241 return connection; 242 } 243 244 @Override onConference(Connection connection1, Connection connection2)245 public void onConference(Connection connection1, Connection connection2) { 246 if (DBG) { 247 Log.d(TAG, "onConference " + connection1 + " " + connection2); 248 } 249 250 BluetoothDevice bd1 = ((HfpClientConnection) connection1).getDevice(); 251 BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice(); 252 // We can only conference two connections on same device 253 if (!Objects.equals(bd1, bd2)) { 254 Log.e(TAG, 255 "Cannot conference calls from two different devices " + "bd1 " + bd1 + " bd2 " 256 + bd2 + " conn1 " + connection1 + "connection2 " + connection2); 257 return; 258 } 259 260 HfpClientDeviceBlock block = findBlockForDevice(bd1); 261 block.onConference(connection1, connection2); 262 } 263 getDevice(PhoneAccountHandle handle)264 private BluetoothDevice getDevice(PhoneAccountHandle handle) { 265 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 266 String btAddr = account.getAddress().getSchemeSpecificPart(); 267 return mAdapter.getRemoteDevice(btAddr); 268 } 269 270 BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() { 271 @Override 272 public void onServiceConnected(int profile, BluetoothProfile proxy) { 273 if (DBG) { 274 Log.d(TAG, "onServiceConnected"); 275 } 276 mHeadsetProfile = (BluetoothHeadsetClient) proxy; 277 278 List<BluetoothDevice> devices = mHeadsetProfile.getConnectedDevices(); 279 if (devices == null) { 280 Log.w(TAG, "No connected or more than one connected devices found." + devices); 281 return; 282 } 283 for (BluetoothDevice device : devices) { 284 if (DBG) { 285 Log.d(TAG, "Creating phone account for device " + device); 286 } 287 288 // Creation of the block takes care of initializing the phone account and 289 // calls. 290 HfpClientDeviceBlock block = createBlockForDevice(device); 291 } 292 } 293 294 @Override 295 public void onServiceDisconnected(int profile) { 296 if (DBG) { 297 Log.d(TAG, "onServiceDisconnected " + profile); 298 } 299 mHeadsetProfile = null; 300 disconnectAll(); 301 } 302 }; 303 304 // Block management functions createBlockForDevice(BluetoothDevice device)305 synchronized HfpClientDeviceBlock createBlockForDevice(BluetoothDevice device) { 306 Log.d(TAG, "Creating block for device " + device); 307 if (mDeviceBlocks.containsKey(device)) { 308 Log.e(TAG, "Device already exists " + device + " blocks " + mDeviceBlocks); 309 return null; 310 } 311 312 HfpClientDeviceBlock block = new HfpClientDeviceBlock(this, device, mHeadsetProfile); 313 mDeviceBlocks.put(device, block); 314 return block; 315 } 316 findBlockForDevice(BluetoothDevice device)317 synchronized HfpClientDeviceBlock findBlockForDevice(BluetoothDevice device) { 318 Log.d(TAG, "Finding block for device " + device + " blocks " + mDeviceBlocks); 319 return mDeviceBlocks.get(device); 320 } 321 findBlockForHandle(PhoneAccountHandle handle)322 synchronized HfpClientDeviceBlock findBlockForHandle(PhoneAccountHandle handle) { 323 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 324 String btAddr = account.getAddress().getSchemeSpecificPart(); 325 BluetoothDevice device = mAdapter.getRemoteDevice(btAddr); 326 Log.d(TAG, "Finding block for handle " + handle + " device " + btAddr); 327 return mDeviceBlocks.get(device); 328 } 329 330 // Util functions that may be used by various classes createAccount(Context context, BluetoothDevice device)331 public static PhoneAccount createAccount(Context context, BluetoothDevice device) { 332 Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null); 333 PhoneAccountHandle handle = 334 new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class), 335 device.getAddress()); 336 337 int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER; 338 if (context.getApplicationContext().getResources().getBoolean( 339 com.android.bluetooth.R.bool 340 .hfp_client_connection_service_support_emergency_call)) { 341 // Need to have an emergency call capability to place emergency call 342 capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS; 343 } 344 345 PhoneAccount account = 346 new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr) 347 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL)) 348 .setCapabilities(capabilities) 349 .build(); 350 if (DBG) { 351 Log.d(TAG, "phoneaccount: " + account); 352 } 353 return account; 354 } 355 hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device)356 public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) { 357 Bundle features = client.getCurrentAgEvents(device); 358 return features != null && features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, 359 false); 360 } 361 } 362