1 /* 2 * Copyright (C) 2021 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.car.bluetooth; 17 18 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 19 20 import android.bluetooth.BluetoothA2dpSink; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadsetClient; 24 import android.bluetooth.BluetoothManager; 25 import android.bluetooth.BluetoothProfile; 26 import android.car.ICarBluetoothUserService; 27 import android.car.builtin.bluetooth.BluetoothHeadsetClientHelper; 28 import android.car.builtin.util.Slogf; 29 import android.telecom.PhoneAccountHandle; 30 import android.telecom.TelecomManager; 31 import android.util.Log; 32 import android.util.SparseBooleanArray; 33 34 import com.android.car.CarLog; 35 import com.android.car.CarPerUserServiceImpl; 36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 37 import com.android.car.internal.util.IndentingPrintWriter; 38 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.Objects; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.locks.Condition; 44 import java.util.concurrent.locks.ReentrantLock; 45 46 /** 47 * This service manages Bluetooth in the context of a particular Android user and provides a surface 48 * by which other users (primarily User 0 in this context) can call into Bluetooth. 49 * 50 * Bluetooth currently runs as the foreground user only, and restricts calls to many Bluetooth APIs 51 * to _only_ the user that Bluetooth runs under. This service allows the User 0 based Car*Services 52 * to make calls on behalf of the foreground user. 53 */ 54 public class CarBluetoothUserService extends ICarBluetoothUserService.Stub { 55 56 private static final String TAG = CarLog.tagFor(CarBluetoothUserService.class); 57 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 58 59 private static final int PROXY_OPERATION_TIMEOUT_MS = 8_000; 60 61 // Profiles we support 62 private static final List<Integer> sProfilesToConnect = Arrays.asList( 63 BluetoothProfile.HEADSET_CLIENT, 64 BluetoothProfile.A2DP_SINK 65 ); 66 67 private final CarPerUserServiceImpl mService; 68 private final BluetoothAdapter mBluetoothAdapter; 69 private final TelecomManager mTelecomManager; 70 71 // Profile Proxies Objects to pair with above list. Access to these proxy objects will all be 72 // guarded by the below mBluetoothProxyLock 73 private BluetoothA2dpSink mBluetoothA2dpSink; 74 private BluetoothHeadsetClient mBluetoothHeadsetClient; 75 76 // Concurrency variables for waitForProxies. Used so we can best effort block with a timeout 77 // while waiting for services to be bound to the proxy objects. 78 private final ReentrantLock mBluetoothProxyLock; 79 private final Condition mConditionAllProxiesConnected; 80 private final FastPairProvider mFastPairProvider; 81 private SparseBooleanArray mBluetoothProfileStatus; 82 private int mConnectedProfiles; 83 84 /** 85 * Create a CarBluetoothUserService instance. 86 * 87 * @param service - A reference to a CarPerUserService, so we can use its context to receive 88 * updates as a particular user. 89 */ CarBluetoothUserService(CarPerUserServiceImpl service)90 public CarBluetoothUserService(CarPerUserServiceImpl service) { 91 mService = service; 92 mConnectedProfiles = 0; 93 mBluetoothProfileStatus = new SparseBooleanArray(); 94 for (int profile : sProfilesToConnect) { 95 mBluetoothProfileStatus.put(profile, false); 96 } 97 mBluetoothProxyLock = new ReentrantLock(); 98 mConditionAllProxiesConnected = mBluetoothProxyLock.newCondition(); 99 mBluetoothAdapter = mService.getApplicationContext() 100 .getSystemService(BluetoothManager.class).getAdapter(); 101 Objects.requireNonNull(mBluetoothAdapter, "Bluetooth adapter cannot be null"); 102 mTelecomManager = mService.getApplicationContext().getSystemService(TelecomManager.class); 103 mFastPairProvider = new FastPairProvider(service); 104 } 105 106 /** 107 * Setup connections to the profile proxy objects that talk to the Bluetooth profile services. 108 * 109 * Proxy references are held by the Bluetooth Framework on our behalf. We will be notified each 110 * time the underlying service connects for each proxy we create. Notifications stop when we 111 * close the proxy. As such, each time this is called we clean up any existing proxies before 112 * creating new ones. 113 */ 114 @Override setupBluetoothConnectionProxies()115 public void setupBluetoothConnectionProxies() { 116 if (DBG) { 117 Slogf.d(TAG, "Initiate connections to profile proxies"); 118 } 119 120 // Clear existing proxy objects 121 closeBluetoothConnectionProxies(); 122 123 // Create proxy for each supported profile. Objects arrive later in the profile listener. 124 // Operations on the proxies expect them to be connected. Functions below should call 125 // waitForProxies() to best effort wait for them to be up if Bluetooth is enabled. 126 for (int profile : sProfilesToConnect) { 127 if (DBG) { 128 Slogf.d(TAG, "Creating proxy for %s", BluetoothUtils.getProfileName(profile)); 129 } 130 mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(), 131 mProfileListener, profile); 132 } 133 mFastPairProvider.start(); 134 } 135 136 /** 137 * Close connections to the profile proxy objects 138 */ 139 @Override closeBluetoothConnectionProxies()140 public void closeBluetoothConnectionProxies() { 141 if (DBG) { 142 Slogf.d(TAG, "Clean up profile proxy objects"); 143 } 144 mBluetoothProxyLock.lock(); 145 try { 146 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink); 147 mBluetoothA2dpSink = null; 148 mBluetoothProfileStatus.put(BluetoothProfile.A2DP_SINK, false); 149 150 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, 151 mBluetoothHeadsetClient); 152 mBluetoothHeadsetClient = null; 153 mBluetoothProfileStatus.put(BluetoothProfile.HEADSET_CLIENT, false); 154 155 mConnectedProfiles = 0; 156 } finally { 157 mBluetoothProxyLock.unlock(); 158 } 159 mFastPairProvider.stop(); 160 } 161 162 /** 163 * Listen for and collect Bluetooth profile proxy connections and disconnections. 164 */ 165 private BluetoothProfile.ServiceListener mProfileListener = 166 new BluetoothProfile.ServiceListener() { 167 public void onServiceConnected(int profile, BluetoothProfile proxy) { 168 if (DBG) { 169 Slogf.d(TAG, "onServiceConnected profile: %s", 170 BluetoothUtils.getProfileName(profile)); 171 } 172 173 // Grab the profile proxy object and update the status book keeping in one step so the 174 // book keeping and proxy objects never disagree 175 mBluetoothProxyLock.lock(); 176 try { 177 switch (profile) { 178 case BluetoothProfile.A2DP_SINK: 179 mBluetoothA2dpSink = (BluetoothA2dpSink) proxy; 180 break; 181 case BluetoothProfile.HEADSET_CLIENT: 182 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy; 183 break; 184 default: 185 if (DBG) { 186 Slogf.d(TAG, "Unsupported profile connected: %s", 187 BluetoothUtils.getProfileName(profile)); 188 } 189 break; 190 } 191 192 if (!mBluetoothProfileStatus.get(profile, false)) { 193 mBluetoothProfileStatus.put(profile, true); 194 mConnectedProfiles++; 195 if (mConnectedProfiles == sProfilesToConnect.size()) { 196 if (DBG) { 197 Slogf.d(TAG, "All profiles have connected"); 198 } 199 mConditionAllProxiesConnected.signal(); 200 } 201 } else { 202 Slogf.w(TAG, "Received duplicate service connection event for: %s", 203 BluetoothUtils.getProfileName(profile)); 204 } 205 } finally { 206 mBluetoothProxyLock.unlock(); 207 } 208 } 209 210 public void onServiceDisconnected(int profile) { 211 if (DBG) { 212 Slogf.d(TAG, "onServiceDisconnected profile: %s", 213 BluetoothUtils.getProfileName(profile)); 214 } 215 mBluetoothProxyLock.lock(); 216 try { 217 if (mBluetoothProfileStatus.get(profile, false)) { 218 mBluetoothProfileStatus.put(profile, false); 219 mConnectedProfiles--; 220 } else { 221 Slogf.w(TAG, "Received duplicate service disconnection event for: %s", 222 BluetoothUtils.getProfileName(profile)); 223 } 224 } finally { 225 mBluetoothProxyLock.unlock(); 226 } 227 } 228 }; 229 230 /** 231 * Check if a proxy is available for the given profile to talk to the Profile's bluetooth 232 * service. 233 * 234 * @param profile - Bluetooth profile to check for 235 * @return - true if proxy available, false if not. 236 */ 237 @Override isBluetoothConnectionProxyAvailable(int profile)238 public boolean isBluetoothConnectionProxyAvailable(int profile) { 239 if (!mBluetoothAdapter.isEnabled()) return false; 240 boolean proxyConnected = false; 241 mBluetoothProxyLock.lock(); 242 try { 243 proxyConnected = mBluetoothProfileStatus.get(profile, false); 244 } finally { 245 mBluetoothProxyLock.unlock(); 246 } 247 return proxyConnected; 248 } 249 250 /** 251 * Wait for the proxy objects to be up for all profiles, with a timeout. 252 * 253 * @param timeout Amount of time in milliseconds to wait for giving up on the wait operation 254 * @return True if the condition was satisfied within the timeout, False otherwise 255 */ waitForProxies(int timeout )256 private boolean waitForProxies(int timeout /* ms */) { 257 if (DBG) { 258 Slogf.d(TAG, "waitForProxies()"); 259 } 260 // If bluetooth isn't on then the operation waiting on proxies was never meant to actually 261 // work regardless if Bluetooth comes on within the timeout period or not. Return false. 262 if (!mBluetoothAdapter.isEnabled()) return false; 263 try { 264 while (mConnectedProfiles != sProfilesToConnect.size()) { 265 if (!mConditionAllProxiesConnected.await( 266 timeout, TimeUnit.MILLISECONDS)) { 267 Slogf.e(TAG, "Timeout while waiting for proxies, Connected: %d/%d", 268 mConnectedProfiles, sProfilesToConnect.size()); 269 return false; 270 } 271 } 272 } catch (InterruptedException e) { 273 Slogf.w(TAG, "waitForProxies: interrupted", e); 274 Thread.currentThread().interrupt(); 275 return false; 276 } 277 return true; 278 } 279 280 /** 281 * Get the connection policy of the given Bluetooth profile for the given remote device 282 * 283 * @param profile - Bluetooth profile 284 * @param device - remote Bluetooth device 285 */ 286 @Override getConnectionPolicy(int profile, BluetoothDevice device)287 public int getConnectionPolicy(int profile, BluetoothDevice device) { 288 if (device == null) { 289 Slogf.e(TAG, "Cannot get %s profile connection policy on null device", 290 BluetoothUtils.getProfileName(profile)); 291 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 292 } 293 int policy; 294 mBluetoothProxyLock.lock(); 295 try { 296 if (!isBluetoothConnectionProxyAvailable(profile)) { 297 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS) 298 && !isBluetoothConnectionProxyAvailable(profile)) { 299 Slogf.e(TAG, "Cannot get %s profile connection policy. Proxy Unavailable", 300 BluetoothUtils.getProfileName(profile)); 301 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 302 } 303 } 304 switch (profile) { 305 case BluetoothProfile.A2DP_SINK: 306 policy = mBluetoothA2dpSink.getConnectionPolicy(device); 307 break; 308 case BluetoothProfile.HEADSET_CLIENT: 309 policy = mBluetoothHeadsetClient.getConnectionPolicy(device); 310 break; 311 default: 312 Slogf.w(TAG, "Unsupported Profile: %s", BluetoothUtils.getProfileName(profile)); 313 policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 314 break; 315 } 316 } finally { 317 mBluetoothProxyLock.unlock(); 318 } 319 if (DBG) { 320 Slogf.d(TAG, "%s connection policy for %s (%s) = %d", 321 BluetoothUtils.getProfileName(profile), 322 device.getName(), device.getAddress(), policy); 323 } 324 return policy; 325 } 326 327 /** 328 * Set the connection policy of the given Bluetooth profile for the given remote device 329 * 330 * @param profile - Bluetooth profile 331 * @param device - remote Bluetooth device 332 * @param policy - connection policy to set 333 */ 334 @Override setConnectionPolicy(int profile, BluetoothDevice device, int policy)335 public void setConnectionPolicy(int profile, BluetoothDevice device, int policy) { 336 if (device == null) { 337 Slogf.e(TAG, "Cannot set %s profile connection policy on null device", 338 BluetoothUtils.getProfileName(profile)); 339 return; 340 } 341 if (DBG) { 342 Slogf.d(TAG, "Setting %s connection policy for %s (%s) to %d", 343 BluetoothUtils.getProfileName(profile), device.getName(), device.getAddress(), 344 policy); 345 } 346 mBluetoothProxyLock.lock(); 347 try { 348 if (!isBluetoothConnectionProxyAvailable(profile)) { 349 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS) 350 && !isBluetoothConnectionProxyAvailable(profile)) { 351 Slogf.e(TAG, "Cannot set %s profile connection policy. Proxy Unavailable", 352 BluetoothUtils.getProfileName(profile)); 353 return; 354 } 355 } 356 switch (profile) { 357 case BluetoothProfile.A2DP_SINK: 358 mBluetoothA2dpSink.setConnectionPolicy(device, policy); 359 break; 360 case BluetoothProfile.HEADSET_CLIENT: 361 mBluetoothHeadsetClient.setConnectionPolicy(device, policy); 362 break; 363 default: 364 Slogf.w(TAG, "Unsupported Profile: %s", BluetoothUtils.getProfileName(profile)); 365 break; 366 } 367 } finally { 368 mBluetoothProxyLock.unlock(); 369 } 370 } 371 372 /** 373 * Triggers Bluetooth to start a BVRA session. 374 */ startBluetoothVoiceRecognition()375 public boolean startBluetoothVoiceRecognition() { 376 mBluetoothProxyLock.lock(); 377 try { 378 if (mBluetoothHeadsetClient == null) { 379 Slogf.e(TAG, "HFP BVRA, no headsetclient proxy found."); 380 return false; 381 } 382 List<BluetoothDevice> devices = BluetoothHeadsetClientHelper.getConnectedBvraDevices( 383 mBluetoothHeadsetClient); 384 if (devices != null && !devices.isEmpty()) { 385 // Until a UI has been agreed upon that allows a user to select from multiple 386 // devices, a BVRA device will be chosen as follows: 387 // 1. Use the device corresponding to the default phone account. 388 // 2. If that device doesn't support BVRA or if there is no default account, use 389 // the first device that supports BVRA. 390 BluetoothDevice bvraDevice = devices.get(0); 391 392 // {@link TelecomManager#getUserSelectedOutgoingPhoneAccount} returns the 393 // user-chosen default for making outgoing phone calls. This default is set when 394 // {@link HfpClientConnectionService} creates a phone account for a device, via 395 // {@link HfpClientDeviceBlock}. 396 PhoneAccountHandle defaultPhone = 397 mTelecomManager.getUserSelectedOutgoingPhoneAccount(); 398 if (defaultPhone != null) { 399 // When {@link HfpClientConnectionService#createAccount} creates a {@link 400 // PhoneAccountHandle}, it sets the ID to the device's {@code BD_ADDR}. 401 String defaultPhoneBdAddr = defaultPhone.getId(); 402 if (defaultPhoneBdAddr != null) { 403 for (int i = 0; i < devices.size(); i++) { 404 BluetoothDevice d = devices.get(i); 405 if (defaultPhoneBdAddr.equals(d.getAddress())) { 406 bvraDevice = d; 407 break; 408 } 409 } 410 } 411 } 412 413 if (BluetoothHeadsetClientHelper.startVoiceRecognition( 414 mBluetoothHeadsetClient, bvraDevice)) { 415 if (DBG) { 416 Slogf.d(TAG, "HFP BVRA started for %s", bvraDevice.getAddress()); 417 } 418 return true; 419 } else { 420 Slogf.w(TAG, "Unable to start HFP BVRA for %s", bvraDevice.getAddress()); 421 } 422 } else { 423 Slogf.w(TAG, "No devices supporting BVRA found."); 424 } 425 } finally { 426 mBluetoothProxyLock.unlock(); 427 } 428 return false; 429 } 430 431 /** Dump for debugging */ 432 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter pw)433 public void dump(IndentingPrintWriter pw) { 434 pw.printf("Supported profiles: %s\n", sProfilesToConnect); 435 pw.printf("Number of connected profiles: %d\n", mConnectedProfiles); 436 pw.printf("Profiles status: %s\n", mBluetoothProfileStatus); 437 pw.printf("Proxy operation timeout: %d ms\n", PROXY_OPERATION_TIMEOUT_MS); 438 pw.printf("BluetoothAdapter: %s\n", mBluetoothAdapter); 439 pw.printf("BluetoothA2dpSink: %s\n", mBluetoothA2dpSink); 440 pw.printf("BluetoothHeadsetClient: %s\n", mBluetoothHeadsetClient); 441 mFastPairProvider.dump(pw); 442 } 443 } 444