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