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