1 /* 2 * Copyright (C) 2017 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; 17 18 import android.bluetooth.BluetoothA2dpSink; 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadsetClient; 22 import android.bluetooth.BluetoothMapClient; 23 import android.bluetooth.BluetoothPan; 24 import android.bluetooth.BluetoothPbapClient; 25 import android.bluetooth.BluetoothProfile; 26 import android.car.ICarBluetoothUserService; 27 import android.util.IndentingPrintWriter; 28 import android.util.Log; 29 import android.util.Slog; 30 import android.util.SparseBooleanArray; 31 32 import com.android.car.bluetooth.FastPairProvider; 33 34 import java.util.Arrays; 35 import java.util.List; 36 import java.util.Objects; 37 import java.util.concurrent.TimeUnit; 38 import java.util.concurrent.locks.Condition; 39 import java.util.concurrent.locks.ReentrantLock; 40 41 public class CarBluetoothUserService extends ICarBluetoothUserService.Stub { 42 43 private static final String TAG = CarLog.tagFor(CarBluetoothUserService.class); 44 45 private static final int PROXY_OPERATION_TIMEOUT_MS = 8_000; 46 47 // Profiles we support 48 private static final List<Integer> sProfilesToConnect = Arrays.asList( 49 BluetoothProfile.HEADSET_CLIENT, 50 BluetoothProfile.PBAP_CLIENT, 51 BluetoothProfile.A2DP_SINK, 52 BluetoothProfile.MAP_CLIENT, 53 BluetoothProfile.PAN 54 ); 55 56 private final PerUserCarService mService; 57 private final BluetoothAdapter mBluetoothAdapter; 58 59 // Profile Proxies Objects to pair with above list. Access to these proxy objects will all be 60 // guarded by the below mBluetoothProxyLock 61 private BluetoothA2dpSink mBluetoothA2dpSink; 62 private BluetoothHeadsetClient mBluetoothHeadsetClient; 63 private BluetoothPbapClient mBluetoothPbapClient; 64 private BluetoothMapClient mBluetoothMapClient; 65 private BluetoothPan mBluetoothPan; 66 67 // Concurrency variables for waitForProxies. Used so we can best effort block with a timeout 68 // while waiting for services to be bound to the proxy objects. 69 private final ReentrantLock mBluetoothProxyLock; 70 private final Condition mConditionAllProxiesConnected; 71 private final FastPairProvider mFastPairProvider; 72 private SparseBooleanArray mBluetoothProfileStatus; 73 private int mConnectedProfiles; 74 75 /** 76 * Create a CarBluetoothUserService instance. 77 * 78 * @param service - A reference to a PerUserCarService, so we can use its context to receive 79 * updates as a particular user. 80 */ CarBluetoothUserService(PerUserCarService service)81 public CarBluetoothUserService(PerUserCarService service) { 82 mService = service; 83 mConnectedProfiles = 0; 84 mBluetoothProfileStatus = new SparseBooleanArray(); 85 for (int profile : sProfilesToConnect) { 86 mBluetoothProfileStatus.put(profile, false); 87 } 88 mBluetoothProxyLock = new ReentrantLock(); 89 mConditionAllProxiesConnected = mBluetoothProxyLock.newCondition(); 90 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 91 Objects.requireNonNull(mBluetoothAdapter, "Bluetooth adapter cannot be null"); 92 mFastPairProvider = new FastPairProvider(service); 93 } 94 95 /** 96 * Setup connections to the profile proxy objects that talk to the Bluetooth profile services. 97 * 98 * Proxy references are held by the Bluetooth Framework on our behalf. We will be notified each 99 * time the underlying service connects for each proxy we create. Notifications stop when we 100 * close the proxy. As such, each time this is called we clean up any existing proxies before 101 * creating new ones. 102 */ 103 @Override setupBluetoothConnectionProxies()104 public void setupBluetoothConnectionProxies() { 105 logd("Initiate connections to profile proxies"); 106 107 // Clear existing proxy objects 108 closeBluetoothConnectionProxies(); 109 110 // Create proxy for each supported profile. Objects arrive later in the profile listener. 111 // Operations on the proxies expect them to be connected. Functions below should call 112 // waitForProxies() to best effort wait for them to be up if Bluetooth is enabled. 113 for (int profile : sProfilesToConnect) { 114 logd("Creating proxy for %s", Utils.getProfileName(profile)); 115 mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(), 116 mProfileListener, profile); 117 } 118 mFastPairProvider.start(); 119 } 120 121 /** 122 * Close connections to the profile proxy objects 123 */ 124 @Override closeBluetoothConnectionProxies()125 public void closeBluetoothConnectionProxies() { 126 logd("Clean up profile proxy objects"); 127 mBluetoothProxyLock.lock(); 128 try { 129 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink); 130 mBluetoothA2dpSink = null; 131 mBluetoothProfileStatus.put(BluetoothProfile.A2DP_SINK, false); 132 133 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, 134 mBluetoothHeadsetClient); 135 mBluetoothHeadsetClient = null; 136 mBluetoothProfileStatus.put(BluetoothProfile.HEADSET_CLIENT, false); 137 138 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient); 139 mBluetoothPbapClient = null; 140 mBluetoothProfileStatus.put(BluetoothProfile.PBAP_CLIENT, false); 141 142 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient); 143 mBluetoothMapClient = null; 144 mBluetoothProfileStatus.put(BluetoothProfile.MAP_CLIENT, false); 145 146 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan); 147 mBluetoothPan = null; 148 mBluetoothProfileStatus.put(BluetoothProfile.PAN, 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 logd("onServiceConnected profile: %s", Utils.getProfileName(profile)); 164 165 // Grab the profile proxy object and update the status book keeping in one step so the 166 // book keeping and proxy objects never disagree 167 mBluetoothProxyLock.lock(); 168 try { 169 switch (profile) { 170 case BluetoothProfile.A2DP_SINK: 171 mBluetoothA2dpSink = (BluetoothA2dpSink) proxy; 172 break; 173 case BluetoothProfile.HEADSET_CLIENT: 174 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy; 175 break; 176 case BluetoothProfile.PBAP_CLIENT: 177 mBluetoothPbapClient = (BluetoothPbapClient) proxy; 178 break; 179 case BluetoothProfile.MAP_CLIENT: 180 mBluetoothMapClient = (BluetoothMapClient) proxy; 181 break; 182 case BluetoothProfile.PAN: 183 mBluetoothPan = (BluetoothPan) proxy; 184 break; 185 default: 186 logd("Unhandled profile connected: %s", Utils.getProfileName(profile)); 187 break; 188 } 189 190 if (!mBluetoothProfileStatus.get(profile, false)) { 191 mBluetoothProfileStatus.put(profile, true); 192 mConnectedProfiles++; 193 if (mConnectedProfiles == sProfilesToConnect.size()) { 194 logd("All profiles have connected"); 195 mConditionAllProxiesConnected.signal(); 196 } 197 } else { 198 Slog.w(TAG, "Received duplicate service connection event for: " 199 + Utils.getProfileName(profile)); 200 } 201 } finally { 202 mBluetoothProxyLock.unlock(); 203 } 204 } 205 206 public void onServiceDisconnected(int profile) { 207 logd("onServiceDisconnected profile: %s", Utils.getProfileName(profile)); 208 mBluetoothProxyLock.lock(); 209 try { 210 if (mBluetoothProfileStatus.get(profile, false)) { 211 mBluetoothProfileStatus.put(profile, false); 212 mConnectedProfiles--; 213 } else { 214 Slog.w(TAG, "Received duplicate service disconnection event for: " 215 + Utils.getProfileName(profile)); 216 } 217 } finally { 218 mBluetoothProxyLock.unlock(); 219 } 220 } 221 }; 222 223 /** 224 * Check if a proxy is available for the given profile to talk to the Profile's bluetooth 225 * service. 226 * 227 * @param profile - Bluetooth profile to check for 228 * @return - true if proxy available, false if not. 229 */ 230 @Override isBluetoothConnectionProxyAvailable(int profile)231 public boolean isBluetoothConnectionProxyAvailable(int profile) { 232 if (!mBluetoothAdapter.isEnabled()) return false; 233 boolean proxyConnected = false; 234 mBluetoothProxyLock.lock(); 235 try { 236 proxyConnected = mBluetoothProfileStatus.get(profile, false); 237 } finally { 238 mBluetoothProxyLock.unlock(); 239 } 240 return proxyConnected; 241 } 242 243 /** 244 * Wait for the proxy objects to be up for all profiles, with a timeout. 245 * 246 * @param timeout Amount of time in milliseconds to wait for giving up on the wait operation 247 * @return True if the condition was satisfied within the timeout, False otherwise 248 */ waitForProxies(int timeout )249 private boolean waitForProxies(int timeout /* ms */) { 250 logd("waitForProxies()"); 251 // If bluetooth isn't on then the operation waiting on proxies was never meant to actually 252 // work regardless if Bluetooth comes on within the timeout period or not. Return false. 253 if (!mBluetoothAdapter.isEnabled()) return false; 254 try { 255 while (mConnectedProfiles != sProfilesToConnect.size()) { 256 if (!mConditionAllProxiesConnected.await( 257 timeout, TimeUnit.MILLISECONDS)) { 258 Slog.e(TAG, "Timeout while waiting for proxies, Connected: " 259 + mConnectedProfiles + "/" + sProfilesToConnect.size()); 260 return false; 261 } 262 } 263 } catch (InterruptedException e) { 264 Slog.w(TAG, "waitForProxies: interrupted", e); 265 Thread.currentThread().interrupt(); 266 return false; 267 } 268 return true; 269 } 270 271 /** 272 * Connect a given remote device on a specific Bluetooth profile 273 * 274 * @param profile BluetoothProfile.* based profile ID 275 * @param device The device you wish to connect 276 */ 277 @Override bluetoothConnectToProfile(int profile, BluetoothDevice device)278 public boolean bluetoothConnectToProfile(int profile, BluetoothDevice device) { 279 if (device == null) { 280 Slog.e(TAG, "Cannot connect to profile on null device"); 281 return false; 282 } 283 logd("Trying to connect to %s (%s) Profile: %s", device.getName(), device.getAddress(), 284 Utils.getProfileName(profile)); 285 mBluetoothProxyLock.lock(); 286 try { 287 if (!isBluetoothConnectionProxyAvailable(profile)) { 288 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS) 289 && !isBluetoothConnectionProxyAvailable(profile)) { 290 Slog.e(TAG, "Cannot connect to Profile. Proxy Unavailable"); 291 return false; 292 } 293 } 294 switch (profile) { 295 case BluetoothProfile.A2DP_SINK: 296 return mBluetoothA2dpSink.connect(device); 297 case BluetoothProfile.HEADSET_CLIENT: 298 return mBluetoothHeadsetClient.connect(device); 299 case BluetoothProfile.MAP_CLIENT: 300 return mBluetoothMapClient.connect(device); 301 case BluetoothProfile.PBAP_CLIENT: 302 return mBluetoothPbapClient.connect(device); 303 case BluetoothProfile.PAN: 304 return mBluetoothPan.connect(device); 305 default: 306 Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile)); 307 break; 308 } 309 } finally { 310 mBluetoothProxyLock.unlock(); 311 } 312 return false; 313 } 314 315 /** 316 * Disonnect a given remote device from a specific Bluetooth profile 317 * 318 * @param profile BluetoothProfile.* based profile ID 319 * @param device The device you wish to disconnect 320 */ 321 @Override bluetoothDisconnectFromProfile(int profile, BluetoothDevice device)322 public boolean bluetoothDisconnectFromProfile(int profile, BluetoothDevice device) { 323 if (device == null) { 324 Slog.e(TAG, "Cannot disconnect from profile on null device"); 325 return false; 326 } 327 logd("Trying to disconnect from %s (%s) Profile: %s", device.getName(), device.getAddress(), 328 Utils.getProfileName(profile)); 329 mBluetoothProxyLock.lock(); 330 try { 331 if (!isBluetoothConnectionProxyAvailable(profile)) { 332 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS) 333 && !isBluetoothConnectionProxyAvailable(profile)) { 334 Slog.e(TAG, "Cannot disconnect from Profile. Proxy Unavailable"); 335 return false; 336 } 337 } 338 switch (profile) { 339 case BluetoothProfile.A2DP_SINK: 340 return mBluetoothA2dpSink.disconnect(device); 341 case BluetoothProfile.HEADSET_CLIENT: 342 return mBluetoothHeadsetClient.disconnect(device); 343 case BluetoothProfile.MAP_CLIENT: 344 return mBluetoothMapClient.disconnect(device); 345 case BluetoothProfile.PBAP_CLIENT: 346 return mBluetoothPbapClient.disconnect(device); 347 case BluetoothProfile.PAN: 348 return mBluetoothPan.disconnect(device); 349 default: 350 Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile)); 351 break; 352 } 353 } finally { 354 mBluetoothProxyLock.unlock(); 355 } 356 return false; 357 } 358 359 /** 360 * Get the priority of the given Bluetooth profile for the given remote device 361 * 362 * @param profile - Bluetooth profile 363 * @param device - remote Bluetooth device 364 */ 365 @Override getProfilePriority(int profile, BluetoothDevice device)366 public int getProfilePriority(int profile, BluetoothDevice device) { 367 if (device == null) { 368 Slog.e(TAG, "Cannot get " + Utils.getProfileName(profile) 369 + " profile priority on null device"); 370 return BluetoothProfile.PRIORITY_UNDEFINED; 371 } 372 int priority; 373 mBluetoothProxyLock.lock(); 374 try { 375 if (!isBluetoothConnectionProxyAvailable(profile)) { 376 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS) 377 && !isBluetoothConnectionProxyAvailable(profile)) { 378 Slog.e(TAG, "Cannot get " + Utils.getProfileName(profile) 379 + " profile priority. Proxy Unavailable"); 380 return BluetoothProfile.PRIORITY_UNDEFINED; 381 } 382 } 383 switch (profile) { 384 case BluetoothProfile.A2DP_SINK: 385 priority = mBluetoothA2dpSink.getPriority(device); 386 break; 387 case BluetoothProfile.HEADSET_CLIENT: 388 priority = mBluetoothHeadsetClient.getPriority(device); 389 break; 390 case BluetoothProfile.MAP_CLIENT: 391 priority = mBluetoothMapClient.getPriority(device); 392 break; 393 case BluetoothProfile.PBAP_CLIENT: 394 priority = mBluetoothPbapClient.getPriority(device); 395 break; 396 default: 397 Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile)); 398 priority = BluetoothProfile.PRIORITY_UNDEFINED; 399 break; 400 } 401 } finally { 402 mBluetoothProxyLock.unlock(); 403 } 404 logd("%s priority for %s (%s) = %d", Utils.getProfileName(profile), device.getName(), 405 device.getAddress(), priority); 406 return priority; 407 } 408 409 /** 410 * Set the priority of the given Bluetooth profile for the given remote device 411 * 412 * @param profile - Bluetooth profile 413 * @param device - remote Bluetooth device 414 * @param priority - priority to set 415 */ 416 @Override setProfilePriority(int profile, BluetoothDevice device, int priority)417 public void setProfilePriority(int profile, BluetoothDevice device, int priority) { 418 if (device == null) { 419 Slog.e(TAG, "Cannot set " + Utils.getProfileName(profile) 420 + " profile priority on null device"); 421 return; 422 } 423 logd("Setting %s priority for %s (%s) to %d", Utils.getProfileName(profile), 424 device.getName(), device.getAddress(), priority); 425 mBluetoothProxyLock.lock(); 426 try { 427 if (!isBluetoothConnectionProxyAvailable(profile)) { 428 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS) 429 && !isBluetoothConnectionProxyAvailable(profile)) { 430 Slog.e(TAG, "Cannot set " + Utils.getProfileName(profile) 431 + " profile priority. Proxy Unavailable"); 432 return; 433 } 434 } 435 switch (profile) { 436 case BluetoothProfile.A2DP_SINK: 437 mBluetoothA2dpSink.setPriority(device, priority); 438 break; 439 case BluetoothProfile.HEADSET_CLIENT: 440 mBluetoothHeadsetClient.setPriority(device, priority); 441 break; 442 case BluetoothProfile.MAP_CLIENT: 443 mBluetoothMapClient.setPriority(device, priority); 444 break; 445 case BluetoothProfile.PBAP_CLIENT: 446 mBluetoothPbapClient.setPriority(device, priority); 447 break; 448 default: 449 Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile)); 450 break; 451 } 452 } finally { 453 mBluetoothProxyLock.unlock(); 454 } 455 } 456 dump(IndentingPrintWriter pw)457 void dump(IndentingPrintWriter pw) { 458 pw.printf("Supported profiles: %s\n", sProfilesToConnect); 459 pw.printf("Number of connected profiles: %d\n", mConnectedProfiles); 460 pw.printf("Profiles status: %s\n", mBluetoothProfileStatus); 461 pw.printf("Proxy operation timeout: %d ms\n", PROXY_OPERATION_TIMEOUT_MS); 462 pw.printf("BluetoothAdapter: %s\n", mBluetoothAdapter); 463 pw.printf("BluetoothA2dpSink: %s\n", mBluetoothA2dpSink); 464 pw.printf("BluetoothHeadsetClient: %s\n", mBluetoothHeadsetClient); 465 pw.printf("BluetoothPbapClient: %s\n", mBluetoothPbapClient); 466 pw.printf("BluetoothMapClient: %s\n", mBluetoothMapClient); 467 pw.printf("BluetoothPan: %s\n", mBluetoothPan); 468 mFastPairProvider.dump(pw); 469 } 470 471 /** 472 * Log to debug if debug output is enabled 473 */ logd(String message, Object... args)474 private void logd(String message, Object... args) { 475 if (Log.isLoggable(TAG, Log.DEBUG)) { 476 Slog.d(TAG, String.format(message, args)); 477 } 478 } 479 } 480