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