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