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