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.app.ActivityManager; 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothProfile; 21 import android.car.ICarBluetooth; 22 import android.car.ICarBluetoothUserService; 23 import android.car.IPerUserCarService; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.os.IBinder; 27 import android.os.RemoteException; 28 import android.os.UserHandle; 29 import android.util.Log; 30 import android.util.SparseArray; 31 32 import com.android.internal.annotations.GuardedBy; 33 34 import java.io.PrintWriter; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.List; 38 39 /** 40 * CarBluetoothService - Maintains the current user's Bluetooth devices and profile connections. 41 * 42 * For each user, creates: 43 * 1) Set of {@link BluetoothProfileDeviceManager} objects, responsible for maintaining a list of 44 * the user's known devices. These provide an interface to auto-connect devices for the given 45 * profile. 46 * 2) A {@link BluetoothProfileInhibitManager} object that will maintain a set of inhibited 47 * profiles for each device, keeping a device from connecting on those profiles. This provides 48 * an interface to request and release inhibits. 49 * 3) A {@link BluetoothDeviceConnectionPolicy} object, representing a default implementation of 50 * a policy based method of determining and requesting times to auto-connect devices. This 51 * default is controllable through a resource overlay if one chooses to implement their own. 52 * 53 * Provides an interface for other programs to request auto connections. 54 */ 55 public class CarBluetoothService extends ICarBluetooth.Stub implements CarServiceBase { 56 private static final String TAG = "CarBluetoothService"; 57 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 58 private final Context mContext; 59 60 // The list of profiles we wish to manage 61 private static final List<Integer> sManagedProfiles = Arrays.asList( 62 BluetoothProfile.HEADSET_CLIENT, 63 BluetoothProfile.PBAP_CLIENT, 64 BluetoothProfile.A2DP_SINK, 65 BluetoothProfile.MAP_CLIENT, 66 BluetoothProfile.PAN 67 ); 68 69 // Each time PerUserCarService connects we need to get new Bluetooth profile proxies and refresh 70 // all our internal objects to use them. When it disconnects we're to assume our proxies are 71 // invalid. This lock protects all our internal objects. 72 private final Object mPerUserLock = new Object(); 73 74 // Set of Bluetooth Profile Device Managers, own the priority connection lists, updated on user 75 // switch 76 private final SparseArray<BluetoothProfileDeviceManager> mProfileDeviceManagers = 77 new SparseArray<>(); 78 79 // Profile-Inhibit Manager that will temporarily inhibit connections on profiles, per user 80 @GuardedBy("mPerUserLock") 81 private BluetoothProfileInhibitManager mInhibitManager = null; 82 83 // Default Bluetooth device connection policy, per user, enabled with an overlay 84 private final boolean mUseDefaultPolicy; 85 @GuardedBy("mPerUserLock") 86 private BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy = null; 87 88 // Listen for user switch events from the PerUserCarService 89 @GuardedBy("mPerUserLock") 90 private int mUserId; 91 @GuardedBy("mPerUserLock") 92 private IPerUserCarService mPerUserCarService; 93 @GuardedBy("mPerUserLock") 94 private ICarBluetoothUserService mCarBluetoothUserService; 95 private final PerUserCarServiceHelper mUserServiceHelper; 96 private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback = 97 new PerUserCarServiceHelper.ServiceCallback() { 98 @Override 99 public void onServiceConnected(IPerUserCarService perUserCarService) { 100 logd("Connected to PerUserCarService"); 101 synchronized (mPerUserLock) { 102 // Explicitly clear out existing per-user objects since we can't rely on the 103 // onServiceDisconnected and onPreUnbind calls to always be called before this 104 destroyUserLocked(); 105 106 mPerUserCarService = perUserCarService; 107 108 // Create new objects with our new set of profile proxies 109 initializeUserLocked(); 110 } 111 } 112 113 @Override 114 public void onPreUnbind() { 115 logd("Before Unbinding from PerUserCarService"); 116 synchronized (mPerUserLock) { 117 destroyUserLocked(); 118 } 119 } 120 121 @Override 122 public void onServiceDisconnected() { 123 logd("Disconnected from PerUserCarService"); 124 synchronized (mPerUserLock) { 125 destroyUserLocked(); 126 } 127 } 128 }; 129 130 /** 131 * Create an instance of CarBluetoothService 132 * 133 * @param context - A Context object representing the context you want this service to run 134 * @param userSwitchService - An instance of PerUserCarServiceHelper that we can bind a listener 135 * to in order to receive user switch events 136 */ CarBluetoothService(Context context, PerUserCarServiceHelper userSwitchService)137 public CarBluetoothService(Context context, PerUserCarServiceHelper userSwitchService) { 138 mUserId = UserHandle.USER_NULL; 139 mContext = context; 140 mUserServiceHelper = userSwitchService; 141 mUseDefaultPolicy = mContext.getResources().getBoolean( 142 R.bool.useDefaultBluetoothConnectionPolicy); 143 } 144 145 /** 146 * Complete all necessary initialization keeping this service from being running. 147 * 148 * Wait for the user service helper to report a user before initializing a user. 149 */ 150 @Override init()151 public void init() { 152 logd("init()"); 153 mUserServiceHelper.registerServiceCallback(mUserServiceCallback); 154 } 155 156 /** 157 * Release all resources required to run this service and stop running. 158 * 159 * Clean up the user context once we've detached from the user service helper, if any. 160 */ 161 @Override release()162 public void release() { 163 logd("release()"); 164 mUserServiceHelper.unregisterServiceCallback(mUserServiceCallback); 165 synchronized (mPerUserLock) { 166 destroyUserLocked(); 167 } 168 } 169 170 /** 171 * Returns the list of profiles that the Autoconnection policy attempts to connect on 172 * 173 * @return The list of managed Bluetooth profiles 174 */ getManagedProfiles()175 public static List<Integer> getManagedProfiles() { 176 return sManagedProfiles; 177 } 178 179 /** 180 * Initialize the user context using the current active user. 181 * 182 * Only call this following a known user switch once we've connected to the user service helper. 183 */ initializeUserLocked()184 private void initializeUserLocked() { 185 logd("Initializing new user"); 186 mUserId = ActivityManager.getCurrentUser(); 187 createBluetoothUserServiceLocked(); 188 createBluetoothProfileDeviceManagersLocked(); 189 createBluetoothProfileInhibitManagerLocked(); 190 191 // Determine if we need to begin the default policy 192 mBluetoothDeviceConnectionPolicy = null; 193 if (mUseDefaultPolicy) { 194 createBluetoothDeviceConnectionPolicyLocked(); 195 } 196 logd("Switched to user " + mUserId); 197 } 198 199 /** 200 * Destroy the current user context, defined by the set of profile proxies, profile device 201 * managers, inhibit manager and the policy. 202 */ destroyUserLocked()203 private void destroyUserLocked() { 204 logd("Destroying user " + mUserId); 205 destroyBluetoothDeviceConnectionPolicyLocked(); 206 destroyBluetoothProfileInhibitManagerLocked(); 207 destroyBluetoothProfileDeviceManagersLocked(); 208 destroyBluetoothUserServiceLocked(); 209 mPerUserCarService = null; 210 mUserId = UserHandle.USER_NULL; 211 } 212 213 /** 214 * Sets the Per User Car Bluetooth Service (ICarBluetoothService) from the PerUserCarService 215 * which acts as a top level Service running in the current user context. 216 * Also sets up the connection proxy objects required to communicate with the Bluetooth 217 * Profile Services. 218 */ createBluetoothUserServiceLocked()219 private void createBluetoothUserServiceLocked() { 220 if (mPerUserCarService != null) { 221 try { 222 mCarBluetoothUserService = mPerUserCarService.getBluetoothUserService(); 223 mCarBluetoothUserService.setupBluetoothConnectionProxies(); 224 } catch (RemoteException e) { 225 Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: " 226 + e.getMessage()); 227 } catch (java.lang.NullPointerException e) { 228 Log.e(TAG, "Initialization Failed: " + e.getMessage()); 229 } 230 } else { 231 logd("PerUserCarService not connected. Cannot get bluetooth user proxy objects"); 232 } 233 } 234 235 /** 236 * Close out the Per User Car Bluetooth profile proxy connections and destroys the Car Bluetooth 237 * User Service object. 238 */ destroyBluetoothUserServiceLocked()239 private void destroyBluetoothUserServiceLocked() { 240 if (mCarBluetoothUserService == null) return; 241 try { 242 mCarBluetoothUserService.closeBluetoothConnectionProxies(); 243 } catch (RemoteException e) { 244 Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: " 245 + e.getMessage()); 246 } 247 mCarBluetoothUserService = null; 248 } 249 250 /** 251 * Clears out Profile Device Managers and re-creates them for the current user. 252 */ createBluetoothProfileDeviceManagersLocked()253 private void createBluetoothProfileDeviceManagersLocked() { 254 if (mUserId == UserHandle.USER_NULL) { 255 logd("No foreground user, cannot create profile device managers"); 256 return; 257 } 258 for (int profileId : sManagedProfiles) { 259 BluetoothProfileDeviceManager deviceManager = mProfileDeviceManagers.get(profileId); 260 if (deviceManager != null) { 261 deviceManager.stop(); 262 mProfileDeviceManagers.remove(profileId); 263 logd("Existing device manager removed for profile " 264 + Utils.getProfileName(profileId)); 265 } 266 267 deviceManager = BluetoothProfileDeviceManager.create(mContext, mUserId, 268 mCarBluetoothUserService, profileId); 269 if (deviceManager == null) { 270 logd("Failed to create profile device manager for " 271 + Utils.getProfileName(profileId)); 272 continue; 273 } 274 mProfileDeviceManagers.put(profileId, deviceManager); 275 logd("Created profile device manager for " + Utils.getProfileName(profileId)); 276 } 277 278 for (int i = 0; i < mProfileDeviceManagers.size(); i++) { 279 int key = mProfileDeviceManagers.keyAt(i); 280 BluetoothProfileDeviceManager deviceManager = 281 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key); 282 deviceManager.start(); 283 } 284 } 285 286 /** 287 * Stops and clears the entire set of Profile Device Managers. 288 */ destroyBluetoothProfileDeviceManagersLocked()289 private void destroyBluetoothProfileDeviceManagersLocked() { 290 for (int i = 0; i < mProfileDeviceManagers.size(); i++) { 291 int key = mProfileDeviceManagers.keyAt(i); 292 BluetoothProfileDeviceManager deviceManager = 293 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key); 294 deviceManager.stop(); 295 } 296 mProfileDeviceManagers.clear(); 297 } 298 299 /** 300 * Creates an instance of a BluetoothProfileInhibitManager under the current user 301 */ createBluetoothProfileInhibitManagerLocked()302 private void createBluetoothProfileInhibitManagerLocked() { 303 logd("Creating inhibit manager"); 304 if (mUserId == UserHandle.USER_NULL) { 305 logd("No foreground user, cannot create profile inhibit manager"); 306 return; 307 } 308 mInhibitManager = new BluetoothProfileInhibitManager(mContext, mUserId, 309 mCarBluetoothUserService); 310 mInhibitManager.start(); 311 } 312 313 /** 314 * Destroys the current instance of a BluetoothProfileInhibitManager, if one exists 315 */ destroyBluetoothProfileInhibitManagerLocked()316 private void destroyBluetoothProfileInhibitManagerLocked() { 317 logd("Destroying inhibit manager"); 318 if (mInhibitManager == null) return; 319 mInhibitManager.stop(); 320 mInhibitManager = null; 321 } 322 323 /** 324 * Creates an instance of a BluetoothDeviceConnectionPolicy under the current user 325 */ createBluetoothDeviceConnectionPolicyLocked()326 private void createBluetoothDeviceConnectionPolicyLocked() { 327 logd("Creating device connection policy"); 328 if (mUserId == UserHandle.USER_NULL) { 329 logd("No foreground user, cannot create device connection policy"); 330 return; 331 } 332 mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext, 333 mUserId, this); 334 if (mBluetoothDeviceConnectionPolicy == null) { 335 logd("Failed to create default Bluetooth device connection policy."); 336 return; 337 } 338 mBluetoothDeviceConnectionPolicy.init(); 339 } 340 341 /** 342 * Destroys the current instance of a BluetoothDeviceConnectionPolicy, if one exists 343 */ destroyBluetoothDeviceConnectionPolicyLocked()344 private void destroyBluetoothDeviceConnectionPolicyLocked() { 345 logd("Destroying device connection policy"); 346 if (mBluetoothDeviceConnectionPolicy != null) { 347 mBluetoothDeviceConnectionPolicy.release(); 348 mBluetoothDeviceConnectionPolicy = null; 349 } 350 } 351 352 /** 353 * Determine if we are using the default device connection policy or not 354 * 355 * @return true if the default policy is active, false otherwise 356 */ isUsingDefaultConnectionPolicy()357 public boolean isUsingDefaultConnectionPolicy() { 358 synchronized (mPerUserLock) { 359 return mBluetoothDeviceConnectionPolicy != null; 360 } 361 } 362 363 /** 364 * Initiate automatated connecting of devices based on the prioritized device lists for each 365 * profile. 366 */ 367 @Override connectDevices()368 public void connectDevices() { 369 enforceBluetoothAdminPermission(); 370 logd("Connect devices for each profile"); 371 synchronized (mPerUserLock) { 372 for (int i = 0; i < mProfileDeviceManagers.size(); i++) { 373 int key = mProfileDeviceManagers.keyAt(i); 374 BluetoothProfileDeviceManager deviceManager = 375 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key); 376 deviceManager.beginAutoConnecting(); 377 } 378 } 379 } 380 381 /** 382 * Get the Auto Connect priority for a paired Bluetooth Device. 383 * 384 * @param profile - BluetoothProfile to get priority list for 385 * @return A list of BluetoothDevice objects, ordered by highest priority first 386 */ getProfileDevicePriorityList(int profile)387 public List<BluetoothDevice> getProfileDevicePriorityList(int profile) { 388 enforceBluetoothAdminPermission(); 389 synchronized (mPerUserLock) { 390 BluetoothProfileDeviceManager deviceManager = 391 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile); 392 if (deviceManager != null) { 393 return deviceManager.getDeviceListSnapshot(); 394 } 395 } 396 return new ArrayList<BluetoothDevice>(); 397 } 398 399 /** 400 * Get the Auto Connect priority for a paired Bluetooth Device. 401 * 402 * @param profile - BluetoothProfile to get priority for 403 * @param device - Device to get priority for 404 * @return integer priority value, or -1 if no priority available. 405 */ getDeviceConnectionPriority(int profile, BluetoothDevice device)406 public int getDeviceConnectionPriority(int profile, BluetoothDevice device) { 407 enforceBluetoothAdminPermission(); 408 synchronized (mPerUserLock) { 409 BluetoothProfileDeviceManager deviceManager = 410 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile); 411 if (deviceManager != null) { 412 return deviceManager.getDeviceConnectionPriority(device); 413 } 414 } 415 return -1; 416 } 417 418 /** 419 * Set the Auto Connect priority for a paired Bluetooth Device. 420 * 421 * @param profile - BluetoothProfile to set priority for 422 * @param device - Device to set priority (Tag) 423 * @param priority - What priority level to set to 424 */ setDeviceConnectionPriority(int profile, BluetoothDevice device, int priority)425 public void setDeviceConnectionPriority(int profile, BluetoothDevice device, int priority) { 426 enforceBluetoothAdminPermission(); 427 synchronized (mPerUserLock) { 428 BluetoothProfileDeviceManager deviceManager = 429 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile); 430 if (deviceManager != null) { 431 deviceManager.setDeviceConnectionPriority(device, priority); 432 } 433 } 434 return; 435 } 436 437 /** 438 * Request to disconnect the given profile on the given device, and prevent it from reconnecting 439 * until either the request is released, or the process owning the given token dies. 440 * 441 * @param device The device on which to inhibit a profile. 442 * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit. 443 * @param token A {@link IBinder} to be used as an identity for the request. If the process 444 * owning the token dies, the request will automatically be released 445 * @return True if the profile was successfully inhibited, false if an error occurred. 446 */ requestProfileInhibit(BluetoothDevice device, int profile, IBinder token)447 boolean requestProfileInhibit(BluetoothDevice device, int profile, IBinder token) { 448 logd("Request profile inhibit: profile " + Utils.getProfileName(profile) 449 + ", device " + device.getAddress()); 450 synchronized (mPerUserLock) { 451 if (mInhibitManager == null) return false; 452 return mInhibitManager.requestProfileInhibit(device, profile, token); 453 } 454 } 455 456 /** 457 * Undo a previous call to {@link #requestProfileInhibit} with the same parameters, 458 * and reconnect the profile if no other requests are active. 459 * 460 * @param device The device on which to release the inhibit request. 461 * @param profile The profile on which to release the inhibit request. 462 * @param token The token provided in the original call to 463 * {@link #requestBluetoothProfileInhibit}. 464 * @return True if the request was released, false if an error occurred. 465 */ releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token)466 boolean releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token) { 467 logd("Release profile inhibit: profile " + Utils.getProfileName(profile) 468 + ", device " + device.getAddress()); 469 synchronized (mPerUserLock) { 470 if (mInhibitManager == null) return false; 471 return mInhibitManager.releaseProfileInhibit(device, profile, token); 472 } 473 } 474 475 /** 476 * Make sure the caller has the Bluetooth permissions that are required to execute any function 477 */ enforceBluetoothAdminPermission()478 private void enforceBluetoothAdminPermission() { 479 if (mContext != null 480 && PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 481 android.Manifest.permission.BLUETOOTH_ADMIN)) { 482 return; 483 } 484 if (mContext == null) { 485 Log.e(TAG, "CarBluetoothPrioritySettings does not have a Context"); 486 } 487 throw new SecurityException("requires permission " 488 + android.Manifest.permission.BLUETOOTH_ADMIN); 489 } 490 491 /** 492 * Print out the verbose debug status of this object 493 */ 494 @Override dump(PrintWriter writer)495 public void dump(PrintWriter writer) { 496 writer.println("*" + TAG + "*"); 497 synchronized (mPerUserLock) { 498 writer.println("\tUser ID: " + mUserId); 499 writer.println("\tUser Proxies: " + (mCarBluetoothUserService != null ? "Yes" : "No")); 500 501 // Profile Device Manager statuses 502 for (int i = 0; i < mProfileDeviceManagers.size(); i++) { 503 int key = mProfileDeviceManagers.keyAt(i); 504 BluetoothProfileDeviceManager deviceManager = 505 (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key); 506 deviceManager.dump(writer, "\t"); 507 } 508 509 // Profile Inhibits 510 if (mInhibitManager != null) mInhibitManager.dump(writer, "\t"); 511 else writer.println("\tBluetoothProfileInhibitManager: null"); 512 513 // Device Connection Policy 514 writer.println("\tUsing default policy? " + (mUseDefaultPolicy ? "Yes" : "No")); 515 if (mBluetoothDeviceConnectionPolicy == null) { 516 writer.println("\tBluetoothDeviceConnectionPolicy: null"); 517 } else { 518 mBluetoothDeviceConnectionPolicy.dump(writer, "\t"); 519 } 520 } 521 } 522 523 /** 524 * Log to debug if debug output is enabled 525 */ logd(String msg)526 private static void logd(String msg) { 527 if (DBG) { 528 Log.d(TAG, msg); 529 } 530 } 531 } 532