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