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