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 } else if (BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED.equals(action)) { 114 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 115 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 116 BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); 117 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 118 BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); 119 HfpClientDeviceBlock block = findBlockForDevice(device); 120 if (block == null) { 121 Log.w(TAG, "Device audio state changed but no block for device"); 122 return; 123 } 124 block.onAudioStateChange(newState, oldState); 125 } 126 } 127 }; 128 129 @Override onCreate()130 public void onCreate() { 131 super.onCreate(); 132 if (DBG) { 133 Log.d(TAG, "onCreate"); 134 } 135 mAdapter = BluetoothAdapter.getDefaultAdapter(); 136 mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 137 if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts(); 138 mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT); 139 } 140 141 @Override onDestroy()142 public void onDestroy() { 143 if (DBG) { 144 Log.d(TAG, "onDestroy called"); 145 } 146 // Close the profile. 147 if (mHeadsetProfile != null) { 148 mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile); 149 } 150 151 // Unregister the broadcast receiver. 152 try { 153 unregisterReceiver(mBroadcastReceiver); 154 } catch (IllegalArgumentException ex) { 155 Log.w(TAG, "Receiver was not registered."); 156 } 157 158 // Unregister the phone account. This should ideally happen when disconnection ensues but in 159 // case the service crashes we may need to force clean. 160 disconnectAll(); 161 } 162 disconnectAll()163 private synchronized void disconnectAll() { 164 for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it = 165 mDeviceBlocks.entrySet().iterator(); it.hasNext(); ) { 166 it.next().getValue().cleanup(); 167 it.remove(); 168 } 169 } 170 171 @Override onStartCommand(Intent intent, int flags, int startId)172 public int onStartCommand(Intent intent, int flags, int startId) { 173 if (DBG) { 174 Log.d(TAG, "onStartCommand " + intent); 175 } 176 // In order to make sure that the service is sticky (recovers from errors when HFP 177 // connection is still active) and to stop it we need a special intent since stopService 178 // only recreates it. 179 if (intent != null && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, 180 false)) { 181 // Stop the service. 182 stopSelf(); 183 return 0; 184 } else { 185 IntentFilter filter = new IntentFilter(); 186 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 187 filter.addAction(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED); 188 filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED); 189 registerReceiver(mBroadcastReceiver, filter); 190 return START_STICKY; 191 } 192 } 193 194 // This method is called whenever there is a new incoming call (or right after BT connection). 195 @Override onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)196 public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount, 197 ConnectionRequest request) { 198 if (DBG) { 199 Log.d(TAG, 200 "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request); 201 } 202 203 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 204 if (block == null) { 205 Log.w(TAG, "HfpClient does not support having a connection manager"); 206 return null; 207 } 208 209 // We should already have a connection by this time. 210 BluetoothHeadsetClientCall call = 211 request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 212 HfpClientConnection connection = block.onCreateIncomingConnection(call); 213 connection.setHfpClientConnectionService(this); 214 return connection; 215 } 216 217 // This method is called *only if* Dialer UI is used to place an outgoing call. 218 @Override onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)219 public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount, 220 ConnectionRequest request) { 221 if (DBG) { 222 Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount); 223 } 224 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 225 if (block == null) { 226 Log.w(TAG, "HfpClient does not support having a connection manager"); 227 return null; 228 } 229 HfpClientConnection connection = block.onCreateOutgoingConnection(request.getAddress()); 230 connection.setHfpClientConnectionService(this); 231 return connection; 232 } 233 234 // This method is called when: 235 // 1. Outgoing call created from the AG. 236 // 2. Call transfer from AG -> HF (on connection when existed call present). 237 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)238 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount, 239 ConnectionRequest request) { 240 if (DBG) { 241 Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount); 242 } 243 HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount); 244 if (block == null) { 245 Log.w(TAG, "HfpClient does not support having a connection manager"); 246 return null; 247 } 248 249 // We should already have a connection by this time. 250 BluetoothHeadsetClientCall call = 251 request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 252 HfpClientConnection connection = block.onCreateUnknownConnection(call); 253 connection.setHfpClientConnectionService(this); 254 return connection; 255 } 256 257 @Override onConference(Connection connection1, Connection connection2)258 public void onConference(Connection connection1, Connection connection2) { 259 if (DBG) { 260 Log.d(TAG, "onConference " + connection1 + " " + connection2); 261 } 262 263 BluetoothDevice bd1 = ((HfpClientConnection) connection1).getDevice(); 264 BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice(); 265 // We can only conference two connections on same device 266 if (!Objects.equals(bd1, bd2)) { 267 Log.e(TAG, 268 "Cannot conference calls from two different devices " + "bd1 " + bd1 + " bd2 " 269 + bd2 + " conn1 " + connection1 + "connection2 " + connection2); 270 return; 271 } 272 273 HfpClientDeviceBlock block = findBlockForDevice(bd1); 274 block.onConference(connection1, connection2); 275 } 276 getDevice(PhoneAccountHandle handle)277 private BluetoothDevice getDevice(PhoneAccountHandle handle) { 278 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 279 String btAddr = account.getAddress().getSchemeSpecificPart(); 280 return mAdapter.getRemoteDevice(btAddr); 281 } 282 283 BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() { 284 @Override 285 public void onServiceConnected(int profile, BluetoothProfile proxy) { 286 if (DBG) { 287 Log.d(TAG, "onServiceConnected"); 288 } 289 mHeadsetProfile = (BluetoothHeadsetClient) proxy; 290 291 List<BluetoothDevice> devices = mHeadsetProfile.getConnectedDevices(); 292 if (devices == null) { 293 Log.w(TAG, "No connected or more than one connected devices found." + devices); 294 return; 295 } 296 for (BluetoothDevice device : devices) { 297 if (DBG) { 298 Log.d(TAG, "Creating phone account for device " + device); 299 } 300 301 // Creation of the block takes care of initializing the phone account and 302 // calls. 303 HfpClientDeviceBlock block = createBlockForDevice(device); 304 } 305 } 306 307 @Override 308 public void onServiceDisconnected(int profile) { 309 if (DBG) { 310 Log.d(TAG, "onServiceDisconnected " + profile); 311 } 312 mHeadsetProfile = null; 313 disconnectAll(); 314 } 315 }; 316 317 // Block management functions createBlockForDevice(BluetoothDevice device)318 synchronized HfpClientDeviceBlock createBlockForDevice(BluetoothDevice device) { 319 Log.d(TAG, "Creating block for device " + device); 320 if (mDeviceBlocks.containsKey(device)) { 321 Log.e(TAG, "Device already exists " + device + " blocks " + mDeviceBlocks); 322 return null; 323 } 324 325 HfpClientDeviceBlock block = new HfpClientDeviceBlock(this, device, mHeadsetProfile); 326 mDeviceBlocks.put(device, block); 327 return block; 328 } 329 findBlockForDevice(BluetoothDevice device)330 synchronized HfpClientDeviceBlock findBlockForDevice(BluetoothDevice device) { 331 Log.d(TAG, "Finding block for device " + device + " blocks " + mDeviceBlocks); 332 return mDeviceBlocks.get(device); 333 } 334 findBlockForHandle(PhoneAccountHandle handle)335 synchronized HfpClientDeviceBlock findBlockForHandle(PhoneAccountHandle handle) { 336 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 337 String btAddr = account.getAddress().getSchemeSpecificPart(); 338 BluetoothDevice device = mAdapter.getRemoteDevice(btAddr); 339 Log.d(TAG, "Finding block for handle " + handle + " device " + btAddr); 340 return mDeviceBlocks.get(device); 341 } 342 343 // Util functions that may be used by various classes createAccount(Context context, BluetoothDevice device)344 public static PhoneAccount createAccount(Context context, BluetoothDevice device) { 345 Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null); 346 PhoneAccountHandle handle = 347 new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class), 348 device.getAddress()); 349 350 int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER; 351 if (context.getApplicationContext().getResources().getBoolean( 352 com.android.bluetooth.R.bool 353 .hfp_client_connection_service_support_emergency_call)) { 354 // Need to have an emergency call capability to place emergency call 355 capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS; 356 } 357 358 PhoneAccount account = 359 new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr) 360 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL)) 361 .setCapabilities(capabilities) 362 .build(); 363 if (DBG) { 364 Log.d(TAG, "phoneaccount: " + account); 365 } 366 return account; 367 } 368 hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device)369 public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) { 370 Bundle features = client.getCurrentAgEvents(device); 371 return features != null && features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, 372 false); 373 } 374 } 375