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; 17 18 import android.bluetooth.BluetoothAdapter; 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothManager; 21 import android.bluetooth.BluetoothProfile; 22 import android.content.ComponentName; 23 import android.content.Intent; 24 import android.net.Uri; 25 import android.os.ParcelUuid; 26 import android.telecom.Connection; 27 import android.telecom.ConnectionRequest; 28 import android.telecom.ConnectionService; 29 import android.telecom.PhoneAccount; 30 import android.telecom.PhoneAccountHandle; 31 import android.telecom.TelecomManager; 32 import android.util.Log; 33 34 import com.android.bluetooth.btservice.AdapterService; 35 import com.android.bluetooth.pbapclient.PbapClientService; 36 37 import java.util.Arrays; 38 import java.util.HashMap; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 44 public class HfpClientConnectionService extends ConnectionService { 45 private static final String TAG = HfpClientConnectionService.class.getSimpleName(); 46 47 public static final String HFP_SCHEME = "hfpc"; 48 49 private TelecomManager mTelecomManager; 50 51 private HeadsetClientServiceInterface mServiceInterface = new HeadsetClientServiceInterface(); 52 53 private final Map<BluetoothDevice, HfpClientDeviceBlock> mDeviceBlocks = new HashMap<>(); 54 55 // --------------------------------------------------------------------------------------------// 56 // SINGLETON MANAGEMENT // 57 // --------------------------------------------------------------------------------------------// 58 59 private static final Object INSTANCE_LOCK = new Object(); 60 private static HfpClientConnectionService sHfpClientConnectionService; 61 setInstance(HfpClientConnectionService instance)62 private void setInstance(HfpClientConnectionService instance) { 63 synchronized (INSTANCE_LOCK) { 64 sHfpClientConnectionService = instance; 65 } 66 } 67 getInstance()68 private static HfpClientConnectionService getInstance() { 69 synchronized (INSTANCE_LOCK) { 70 return sHfpClientConnectionService; 71 } 72 } 73 clearInstance()74 private void clearInstance() { 75 synchronized (INSTANCE_LOCK) { 76 if (sHfpClientConnectionService == this) { 77 setInstance(null); 78 } 79 } 80 } 81 82 // --------------------------------------------------------------------------------------------// 83 // MESSAGES FROM HEADSET CLIENT SERVICE // 84 // --------------------------------------------------------------------------------------------// 85 86 /** Send a device connection state changed event to this service */ onConnectionStateChanged( BluetoothDevice device, int newState, int oldState)87 public static void onConnectionStateChanged( 88 BluetoothDevice device, int newState, int oldState) { 89 HfpClientConnectionService service = getInstance(); 90 if (service == null) { 91 Log.e(TAG, "onConnectionStateChanged: HFP Client Connection Service not started"); 92 return; 93 } 94 service.onConnectionStateChangedInternal(device, newState, oldState); 95 } 96 97 /** Send a device call state changed event to this service */ onCallChanged(BluetoothDevice device, HfpClientCall call)98 public static void onCallChanged(BluetoothDevice device, HfpClientCall call) { 99 HfpClientConnectionService service = getInstance(); 100 if (service == null) { 101 Log.e(TAG, "onCallChanged: HFP Client Connection Service not started"); 102 return; 103 } 104 service.onCallChangedInternal(device, call); 105 } 106 107 /** Send a device audio state changed event to this service */ onAudioStateChanged(BluetoothDevice device, int newState, int oldState)108 public static void onAudioStateChanged(BluetoothDevice device, int newState, int oldState) { 109 HfpClientConnectionService service = getInstance(); 110 if (service == null) { 111 Log.e(TAG, "onAudioStateChanged: HFP Client Connection Service not started"); 112 return; 113 } 114 service.onAudioStateChangedInternal(device, newState, oldState); 115 } 116 117 // --------------------------------------------------------------------------------------------// 118 // HANDLE MESSAGES FROM HEADSET CLIENT SERVICE // 119 // --------------------------------------------------------------------------------------------// 120 onConnectionStateChangedInternal( BluetoothDevice device, int newState, int oldState)121 private void onConnectionStateChangedInternal( 122 BluetoothDevice device, int newState, int oldState) { 123 if (newState == BluetoothProfile.STATE_CONNECTED) { 124 Log.d(TAG, "Established connection with " + device); 125 126 HfpClientDeviceBlock block = createBlockForDevice(device); 127 if (block == null) { 128 Log.w(TAG, "Block already exists for device= " + device + ", ignoring."); 129 } 130 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 131 Log.d(TAG, "Disconnecting from " + device); 132 133 // Disconnect any inflight calls from the connection service. 134 synchronized (HfpClientConnectionService.this) { 135 HfpClientDeviceBlock block = mDeviceBlocks.remove(device); 136 if (block == null) { 137 Log.w(TAG, "Disconnect for device but no block, device=" + device); 138 return; 139 } 140 block.cleanup(); 141 } 142 } 143 AdapterService adapterService = AdapterService.getAdapterService(); 144 if (adapterService != null && adapterService.getRemoteDevices() != null) { 145 adapterService 146 .getRemoteDevices() 147 .handleHeadsetClientConnectionStateChanged(device, oldState, newState); 148 } 149 adapterService.notifyProfileConnectionStateChangeToGatt( 150 BluetoothProfile.HEADSET_CLIENT, oldState, newState); 151 if (PbapClientService.getPbapClientService() != null) { 152 PbapClientService.getPbapClientService() 153 .handleHeadsetClientConnectionStateChanged(device, oldState, newState); 154 } 155 if (adapterService != null) { 156 adapterService.updateProfileConnectionAdapterProperties( 157 device, BluetoothProfile.HEADSET_CLIENT, newState, oldState); 158 } 159 } 160 onCallChangedInternal(BluetoothDevice device, HfpClientCall call)161 private void onCallChangedInternal(BluetoothDevice device, HfpClientCall call) { 162 HfpClientDeviceBlock block = findBlockForDevice(device); 163 if (block == null) { 164 Log.w(TAG, "Call changed but no block for device=" + device); 165 return; 166 } 167 168 // If we are not connected, then when we actually do get connected the calls should be added 169 // (see ACTION_CONNECTION_STATE_CHANGED intent above). 170 block.handleCall(call); 171 } 172 onAudioStateChangedInternal(BluetoothDevice device, int newState, int oldState)173 private void onAudioStateChangedInternal(BluetoothDevice device, int newState, int oldState) { 174 HfpClientDeviceBlock block = findBlockForDevice(device); 175 if (block == null) { 176 Log.w(TAG, "Device audio state changed but no block for device=" + device); 177 return; 178 } 179 block.onAudioStateChange(newState, oldState); 180 } 181 182 // --------------------------------------------------------------------------------------------// 183 // SERVICE SETUP AND TEAR DOWN // 184 // --------------------------------------------------------------------------------------------// 185 186 @Override onCreate()187 public void onCreate() { 188 super.onCreate(); 189 Log.d(TAG, "onCreate"); 190 mTelecomManager = getSystemService(TelecomManager.class); 191 if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts(); 192 193 List<BluetoothDevice> devices = mServiceInterface.getConnectedDevices(); 194 if (devices != null) { 195 for (BluetoothDevice device : devices) { 196 createBlockForDevice(device); 197 } 198 } 199 200 setInstance(this); 201 } 202 203 @Override onDestroy()204 public void onDestroy() { 205 Log.d(TAG, "onDestroy called"); 206 207 // Unregister the phone account. This should ideally happen when disconnection ensues but in 208 // case the service crashes we may need to force clean. 209 disconnectAll(); 210 211 clearInstance(); 212 } 213 disconnectAll()214 private synchronized void disconnectAll() { 215 for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it = 216 mDeviceBlocks.entrySet().iterator(); 217 it.hasNext(); ) { 218 it.next().getValue().cleanup(); 219 it.remove(); 220 } 221 } 222 223 @Override onStartCommand(Intent intent, int flags, int startId)224 public int onStartCommand(Intent intent, int flags, int startId) { 225 Log.d(TAG, "onStartCommand " + intent); 226 // In order to make sure that the service is sticky (recovers from errors when HFP 227 // connection is still active) and to stop it we need a special intent since stopService 228 // only recreates it. 229 if (intent != null 230 && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, false)) { 231 // Stop the service. 232 stopSelf(); 233 return 0; 234 } 235 return START_STICKY; 236 } 237 238 // --------------------------------------------------------------------------------------------// 239 // TELECOM CONNECTION SERVICE FUNCTIONS // 240 // --------------------------------------------------------------------------------------------// 241 242 // This method is called whenever there is a new incoming call (or right after BT connection). 243 @Override onCreateIncomingConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)244 public Connection onCreateIncomingConnection( 245 PhoneAccountHandle connectionManagerAccount, ConnectionRequest request) { 246 Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request); 247 248 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 249 if (block == null) { 250 Log.w(TAG, "HfpClient does not support having a connection manager"); 251 return null; 252 } 253 254 // We should already have a connection by this time. 255 ParcelUuid callUuid = 256 request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 257 HfpClientConnection connection = 258 block.onCreateIncomingConnection((callUuid != null ? callUuid.getUuid() : null)); 259 return connection; 260 } 261 262 // This method is called *only if* Dialer UI is used to place an outgoing call. 263 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)264 public Connection onCreateOutgoingConnection( 265 PhoneAccountHandle connectionManagerAccount, ConnectionRequest request) { 266 Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount); 267 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 268 if (block == null) { 269 Log.w(TAG, "HfpClient does not support having a connection manager"); 270 return null; 271 } 272 HfpClientConnection connection = block.onCreateOutgoingConnection(request.getAddress()); 273 return connection; 274 } 275 276 // This method is called when: 277 // 1. Outgoing call created from the AG. 278 // 2. Call transfer from AG -> HF (on connection when existed call present). 279 @Override onCreateUnknownConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)280 public Connection onCreateUnknownConnection( 281 PhoneAccountHandle connectionManagerAccount, ConnectionRequest request) { 282 Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount); 283 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 284 if (block == null) { 285 Log.w(TAG, "HfpClient does not support having a connection manager"); 286 return null; 287 } 288 289 // We should already have a connection by this time. 290 ParcelUuid callUuid = 291 request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 292 HfpClientConnection connection = 293 block.onCreateUnknownConnection((callUuid != null ? callUuid.getUuid() : null)); 294 return connection; 295 } 296 297 @Override onConference(Connection connection1, Connection connection2)298 public void onConference(Connection connection1, Connection connection2) { 299 Log.d(TAG, "onConference " + connection1 + " " + connection2); 300 301 BluetoothDevice bd1 = ((HfpClientConnection) connection1).getDevice(); 302 BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice(); 303 // We can only conference two connections on same device 304 if (!Objects.equals(bd1, bd2)) { 305 Log.e( 306 TAG, 307 "Cannot conference calls from two different devices " 308 + "bd1 " 309 + bd1 310 + " bd2 " 311 + bd2 312 + " conn1 " 313 + connection1 314 + "connection2 " 315 + connection2); 316 return; 317 } 318 319 HfpClientDeviceBlock block = findBlockForDevice(bd1); 320 block.onConference(connection1, connection2); 321 } 322 323 // --------------------------------------------------------------------------------------------// 324 // DEVICE MANAGEMENT // 325 // --------------------------------------------------------------------------------------------// 326 getDevice(PhoneAccountHandle handle)327 private BluetoothDevice getDevice(PhoneAccountHandle handle) { 328 BluetoothAdapter adapter = getSystemService(BluetoothManager.class).getAdapter(); 329 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 330 if (account == null) { 331 return null; 332 } 333 String btAddr = account.getAddress().getSchemeSpecificPart(); 334 return adapter.getRemoteDevice(btAddr); 335 } 336 337 // Block management functions createBlockForDevice(BluetoothDevice device)338 synchronized HfpClientDeviceBlock createBlockForDevice(BluetoothDevice device) { 339 Log.d(TAG, "Creating block for device " + device); 340 if (mDeviceBlocks.containsKey(device)) { 341 Log.e(TAG, "Device already exists " + device + " blocks " + mDeviceBlocks); 342 return null; 343 } 344 345 HfpClientDeviceBlock block = 346 HfpClientDeviceBlock.Factory.build(device, this, mServiceInterface); 347 mDeviceBlocks.put(device, block); 348 return block; 349 } 350 findBlockForDevice(BluetoothDevice device)351 synchronized HfpClientDeviceBlock findBlockForDevice(BluetoothDevice device) { 352 Log.d(TAG, "Finding block for device " + device + " blocks " + mDeviceBlocks); 353 return mDeviceBlocks.get(device); 354 } 355 findBlockForHandle(PhoneAccountHandle handle)356 synchronized HfpClientDeviceBlock findBlockForHandle(PhoneAccountHandle handle) { 357 BluetoothDevice device = getDevice(handle); 358 Log.d(TAG, "Finding block for handle " + handle + " device " + device); 359 return mDeviceBlocks.get(device); 360 } 361 createAccount(BluetoothDevice device)362 PhoneAccount createAccount(BluetoothDevice device) { 363 Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null); 364 PhoneAccountHandle handle = 365 new PhoneAccountHandle( 366 new ComponentName(this, HfpClientConnectionService.class), 367 device.getAddress()); 368 369 int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER; 370 if (getApplicationContext() 371 .getResources() 372 .getBoolean( 373 com.android.bluetooth.R.bool 374 .hfp_client_connection_service_support_emergency_call)) { 375 // Need to have an emergency call capability to place emergency call 376 capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS; 377 } 378 379 PhoneAccount account = 380 new PhoneAccount.Builder(handle, "HFP " + device.toString()) 381 .setAddress(addr) 382 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL)) 383 .setCapabilities(capabilities) 384 .build(); 385 Log.d(TAG, "phoneaccount: " + account); 386 return account; 387 } 388 getDeviceBlocks()389 private Map<BluetoothDevice, HfpClientDeviceBlock> getDeviceBlocks() { 390 return mDeviceBlocks; 391 } 392 393 /** Dump the state of the HfpClientConnectionService and internal objects */ dump(StringBuilder sb)394 public static void dump(StringBuilder sb) { 395 HfpClientConnectionService instance = getInstance(); 396 sb.append(" HfpClientConnectionService:\n"); 397 398 if (instance == null) { 399 sb.append(" null"); 400 } else { 401 sb.append(" Devices:\n"); 402 for (HfpClientDeviceBlock block : instance.getDeviceBlocks().values()) { 403 sb.append(" " + block.toString() + "\n"); 404 } 405 } 406 } 407 } 408