HfpClientConnectionService.java revision ffb10b682610a24d5c54e8828c11276fe5703d65
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 */ 16package com.android.bluetooth.hfpclient.connserv; 17 18import android.bluetooth.BluetoothAdapter; 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothHeadsetClient; 21import android.bluetooth.BluetoothHeadsetClientCall; 22import android.bluetooth.BluetoothProfile; 23import android.content.BroadcastReceiver; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.net.Uri; 29import android.os.Bundle; 30import android.os.Handler; 31import android.telecom.Connection; 32import android.telecom.ConnectionRequest; 33import android.telecom.ConnectionService; 34import android.telecom.PhoneAccount; 35import android.telecom.PhoneAccountHandle; 36import android.telecom.TelecomManager; 37import android.util.Log; 38 39import java.util.Arrays; 40import java.util.ArrayList; 41import java.util.Collection; 42import java.util.HashMap; 43import java.util.List; 44import java.util.Map; 45 46public class HfpClientConnectionService extends ConnectionService { 47 private static final String TAG = "HfpClientConnService"; 48 49 public static final String HFP_SCHEME = "hfpc"; 50 51 private BluetoothAdapter mAdapter; 52 private BluetoothDevice mDevice; 53 private BluetoothHeadsetClient mHeadsetProfile; 54 private TelecomManager mTelecomManager; 55 56 private Map<Uri, HfpClientConnection> mConnections = new HashMap<>(); 57 private HfpClientConference mConference; 58 59 private boolean mPendingAcceptCall; 60 61 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 62 @Override 63 public void onReceive(Context context, Intent intent) { 64 Log.d(TAG, "onReceive " + intent); 65 String action = intent != null ? intent.getAction() : null; 66 67 if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 68 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 69 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 70 71 Log.d(TAG, "Established connection with " + mDevice); 72 if (newState == BluetoothProfile.STATE_CONNECTED) { 73 // Add any existing calls to the telecom stack. 74 if (mHeadsetProfile != null) { 75 List<BluetoothHeadsetClientCall> calls = 76 mHeadsetProfile.getCurrentCalls(mDevice); 77 Log.d(TAG, "Got calls " + calls); 78 if (calls == null) { 79 // We can get null as a return if we are not connected. Hence there may 80 // be a race in getting the broadcast and HFP Client getting 81 // disconnected before broadcast gets delivered. 82 Log.w(TAG, "Got connected but calls were null, ignoring the broadcast"); 83 return; 84 } 85 for (BluetoothHeadsetClientCall call : calls) { 86 handleCall(call); 87 } 88 } else { 89 Log.e(TAG, "headset profile is null, ignoring broadcast."); 90 } 91 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 92 // Disconnect any inflight calls from the connection service. 93 disconnectAll(); 94 } 95 } else if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) { 96 // If we are not connected, then when we actually do get connected -- the calls should 97 // be added (see ACTION_CONNECTION_STATE_CHANGED intent above). 98 handleCall((BluetoothHeadsetClientCall) 99 intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL)); 100 Log.d(TAG, mConnections.size() + " remaining"); 101 } 102 } 103 }; 104 105 @Override 106 public void onCreate() { 107 super.onCreate(); 108 Log.d(TAG, "onCreate"); 109 mAdapter = BluetoothAdapter.getDefaultAdapter(); 110 mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 111 mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT); 112 } 113 114 @Override 115 public void onDestroy() { 116 if (mHeadsetProfile != null) { 117 mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile); 118 } 119 unregisterReceiver(mBroadcastReceiver); 120 } 121 122 @Override 123 public int onStartCommand(Intent intent, int flags, int startId) { 124 Log.d(TAG, "onStartCommand " + intent); 125 String action = intent != null ? intent.getAction() : null; 126 IntentFilter filter = new IntentFilter(); 127 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 128 filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED); 129 registerReceiver(mBroadcastReceiver, filter); 130 return START_STICKY; 131 } 132 133 private void handleCall(BluetoothHeadsetClientCall call) { 134 Log.d(TAG, "Got call " + call); 135 136 Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); 137 HfpClientConnection connection = mConnections.get(number); 138 if (connection != null) { 139 connection.handleCallChanged(call); 140 } 141 142 if (connection == null) { 143 // Create the connection here, trigger Telecom to bind to us. 144 buildConnection(call.getDevice(), call, number); 145 146 PhoneAccountHandle handle = HfpClientConnectionService.getHandle(this); 147 TelecomManager manager = 148 (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 149 150 // Depending on where this call originated make it an incoming call or outgoing 151 // (represented as unknown call in telecom since). Since BluetoothHeadsetClientCall is a 152 // parcelable we simply pack the entire object in there. 153 Bundle b = new Bundle(); 154 if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_DIALING || 155 call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ALERTING || 156 call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) { 157 // This is an outgoing call. Even if it is an active call we do not have a way of 158 // putting that parcelable in a seaprate field. 159 b.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call); 160 manager.addNewUnknownCall(handle, b); 161 } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_INCOMING) { 162 // This is an incoming call. 163 b.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, call); 164 manager.addNewIncomingCall(handle, b); 165 } 166 } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) { 167 Log.d(TAG, "Removing number " + number); 168 mConnections.remove(number); 169 } 170 updateConferenceableConnections(); 171 } 172 173 // This method is called whenever there is a new incoming call (or right after BT connection). 174 @Override 175 public Connection onCreateIncomingConnection( 176 PhoneAccountHandle connectionManagerAccount, 177 ConnectionRequest request) { 178 Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request); 179 if (connectionManagerAccount != null && 180 !getHandle(this).equals(connectionManagerAccount)) { 181 Log.w(TAG, "HfpClient does not support having a connection manager"); 182 return null; 183 } 184 185 // We should already have a connection by this time. 186 BluetoothHeadsetClientCall call = 187 request.getExtras().getParcelable( 188 TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 189 Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); 190 HfpClientConnection connection = mConnections.get(number); 191 192 if (connection != null) { 193 connection.onAdded(); 194 updateConferenceableConnections(); 195 return connection; 196 } else { 197 Log.e(TAG, "Connection should exist in our db, if it doesn't we dont know how to " + 198 "handle this call."); 199 return null; 200 } 201 } 202 203 // This method is called *only if* Dialer UI is used to place an outgoing call. 204 @Override 205 public Connection onCreateOutgoingConnection( 206 PhoneAccountHandle connectionManagerAccount, 207 ConnectionRequest request) { 208 Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount); 209 if (connectionManagerAccount != null && 210 !getHandle(this).equals(connectionManagerAccount)) { 211 Log.w(TAG, "HfpClient does not support having a connection manager"); 212 return null; 213 } 214 215 HfpClientConnection connection = 216 buildConnection(getDevice(request.getAccountHandle()), null, request.getAddress()); 217 connection.onAdded(); 218 return connection; 219 } 220 221 // This method is called when: 222 // 1. Outgoing call created from the AG. 223 // 2. Call transfer from AG -> HF (on connection when existed call present). 224 @Override 225 public Connection onCreateUnknownConnection( 226 PhoneAccountHandle connectionManagerAccount, 227 ConnectionRequest request) { 228 Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount); 229 if (connectionManagerAccount != null && 230 !getHandle(this).equals(connectionManagerAccount)) { 231 Log.w(TAG, "HfpClient does not support having a connection manager"); 232 return null; 233 } 234 235 // We should already have a connection by this time. 236 BluetoothHeadsetClientCall call = 237 request.getExtras().getParcelable( 238 TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 239 Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); 240 HfpClientConnection connection = mConnections.get(number); 241 242 if (connection != null) { 243 connection.onAdded(); 244 updateConferenceableConnections(); 245 return connection; 246 } else { 247 Log.e(TAG, "Connection should exist in our db, if it doesn't we dont know how to " + 248 "handle this call " + call); 249 return null; 250 } 251 } 252 253 @Override 254 public void onConference(Connection connection1, Connection connection2) { 255 Log.d(TAG, "onConference " + connection1 + " " + connection2); 256 if (mConference == null) { 257 BluetoothDevice device = getDevice(getHandle(this)); 258 mConference = new HfpClientConference(getHandle(this), device, mHeadsetProfile); 259 addConference(mConference); 260 } 261 mConference.setActive(); 262 if (connection1.getConference() == null) { 263 mConference.addConnection(connection1); 264 } 265 if (connection2.getConference() == null) { 266 mConference.addConnection(connection2); 267 } 268 } 269 270 private void updateConferenceableConnections() { 271 Collection<HfpClientConnection> all = mConnections.values(); 272 273 List<Connection> held = new ArrayList<>(); 274 List<Connection> active = new ArrayList<>(); 275 List<Connection> group = new ArrayList<>(); 276 for (HfpClientConnection connection : all) { 277 switch (connection.getState()) { 278 case Connection.STATE_ACTIVE: 279 active.add(connection); 280 break; 281 case Connection.STATE_HOLDING: 282 held.add(connection); 283 break; 284 default: 285 break; 286 } 287 if (connection.inConference()) { 288 group.add(connection); 289 } 290 } 291 for (Connection connection : held) { 292 connection.setConferenceableConnections(active); 293 } 294 for (Connection connection : active) { 295 connection.setConferenceableConnections(held); 296 } 297 if (group.size() > 1 && mConference == null) { 298 BluetoothDevice device = getDevice(getHandle(this)); 299 mConference = new HfpClientConference(getHandle(this), device, mHeadsetProfile); 300 if (group.get(0).getState() == Connection.STATE_ACTIVE) { 301 mConference.setActive(); 302 } else { 303 mConference.setOnHold(); 304 } 305 for (Connection connection : group) { 306 mConference.addConnection(connection); 307 } 308 addConference(mConference); 309 } 310 if (mConference != null) { 311 List<Connection> toRemove = new ArrayList<>(); 312 for (Connection connection : mConference.getConnections()) { 313 if (!((HfpClientConnection) connection).inConference()) { 314 toRemove.add(connection); 315 } 316 } 317 for (Connection connection : toRemove) { 318 mConference.removeConnection(connection); 319 } 320 if (mConference.getConnections().size() <= 1) { 321 mConference.destroy(); 322 mConference = null; 323 } else { 324 List<Connection> notConferenced = new ArrayList<>(); 325 for (Connection connection : all) { 326 if (connection.getConference() == null && 327 (connection.getState() == Connection.STATE_HOLDING || 328 connection.getState() == Connection.STATE_ACTIVE)) { 329 if (((HfpClientConnection) connection).inConference()) { 330 mConference.addConnection(connection); 331 } else { 332 notConferenced.add(connection); 333 } 334 } 335 } 336 mConference.setConferenceableConnections(notConferenced); 337 } 338 } 339 } 340 341 private void disconnectAll() { 342 for (HfpClientConnection connection : mConnections.values()) { 343 connection.onHfpDisconnected(); 344 } 345 if (mConference != null) { 346 mConference.destroy(); 347 mConference = null; 348 } 349 } 350 351 private BluetoothDevice getDevice(PhoneAccountHandle handle) { 352 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 353 String btAddr = account.getAddress().getSchemeSpecificPart(); 354 return mAdapter.getRemoteDevice(btAddr); 355 } 356 357 private HfpClientConnection buildConnection( 358 BluetoothDevice device, BluetoothHeadsetClientCall call, Uri number) { 359 Log.d(TAG, "Creating connection on " + device + " for " + call + "/" + number); 360 HfpClientConnection connection = 361 new HfpClientConnection(this, device, mHeadsetProfile, call, number); 362 mConnections.put(number, connection); 363 return connection; 364 } 365 366 BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() { 367 @Override 368 public void onServiceConnected(int profile, BluetoothProfile proxy) { 369 Log.d(TAG, "onServiceConnected"); 370 mHeadsetProfile = (BluetoothHeadsetClient) proxy; 371 372 for (HfpClientConnection connection : mConnections.values()) { 373 connection.onHfpConnected(mHeadsetProfile); 374 } 375 376 List<BluetoothHeadsetClientCall> calls = mHeadsetProfile.getCurrentCalls(mDevice); 377 Log.d(TAG, "Got calls " + calls); 378 if (calls != null) { 379 for (BluetoothHeadsetClientCall call : calls) { 380 handleCall(call); 381 } 382 } 383 384 if (mPendingAcceptCall) { 385 mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE); 386 mPendingAcceptCall = false; 387 } 388 } 389 390 @Override 391 public void onServiceDisconnected(int profile) { 392 Log.d(TAG, "onServiceDisconnected " + profile); 393 mHeadsetProfile = null; 394 disconnectAll(); 395 } 396 }; 397 398 public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) { 399 Bundle features = client.getCurrentAgEvents(device); 400 return features == null ? false : 401 features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, false); 402 } 403 404 public static PhoneAccountHandle getHandle(Context context) { 405 return new PhoneAccountHandle( 406 new ComponentName(context, HfpClientConnectionService.class), "hfp"); 407 } 408 409 public static PhoneAccount getAccount(Context context, BluetoothDevice device) { 410 Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null); 411 PhoneAccount account = 412 new PhoneAccount.Builder(HfpClientConnectionService.getHandle(context), "HFP") 413 .setAddress(addr) 414 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL)) 415 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) 416 .build(); 417 Log.d(TAG, "phoneaccount: " + account); 418 return account; 419 } 420} 421