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 17 package com.android.car; 18 19 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES; 20 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES; 21 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES; 22 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES; 23 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.bluetooth.BluetoothA2dpSink; 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothDevice; 29 import android.bluetooth.BluetoothHeadsetClient; 30 import android.bluetooth.BluetoothMapClient; 31 import android.bluetooth.BluetoothPan; 32 import android.bluetooth.BluetoothPbapClient; 33 import android.bluetooth.BluetoothProfile; 34 import android.bluetooth.BluetoothUuid; 35 import android.car.CarBluetoothManager; 36 import android.car.ICarBluetoothUserService; 37 import android.car.ICarUserService; 38 import android.car.drivingstate.CarUxRestrictions; 39 import android.car.drivingstate.ICarUxRestrictionsChangeListener; 40 import android.car.hardware.CarPropertyValue; 41 import android.car.hardware.property.CarPropertyEvent; 42 import android.car.hardware.property.ICarPropertyEventListener; 43 import android.content.BroadcastReceiver; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.hardware.automotive.vehicle.V2_0.VehicleIgnitionState; 48 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 49 import android.os.ParcelUuid; 50 import android.os.Parcelable; 51 import android.os.RemoteException; 52 import android.os.UserHandle; 53 import android.provider.Settings; 54 import android.util.Log; 55 56 import com.android.internal.annotations.VisibleForTesting; 57 58 import java.io.PrintWriter; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Set; 66 import java.util.concurrent.locks.ReentrantLock; 67 68 69 /** 70 * A Bluetooth Device Connection policy that is specific to the use cases of a Car. A car's 71 * bluetooth capabilities in terms of the profiles it supports and its use cases are unique. 72 * Hence the CarService manages the policy that drives when and what to connect to. 73 * 74 * When to connect: 75 * The policy can be configured to listen to various vehicle events that are appropriate to 76 * trigger a connection attempt. Signals like door unlock/open, ignition state changes indicate 77 * user entry and there by attempt to connect to their devices. This removes the need for the user 78 * to manually connect his device everytime they get in a car. 79 * 80 * Which device to connect: 81 * The policy also keeps track of the {Profile : DevicesThatCanConnectOnTheProfile} and when 82 * it is time to connect, picks the device that is appropriate and available. 83 * For every profile, the policy attempts to connect to the last connected device first. The policy 84 * maintains a list of connect-able devices for every profile, in the order of how recently they 85 * connected. The device that successfully connects on a profile is moved to the top of the list 86 * of devices for that profile, so the next time a connection attempt is made, the policy starts 87 * with the last connected device first. 88 */ 89 90 public class BluetoothDeviceConnectionPolicy { 91 private static final String TAG = "BTDevConnectionPolicy"; 92 private static final String SETTINGS_DELIMITER = ","; 93 private static final boolean DBG = Utils.DBG; 94 private final Context mContext; 95 private boolean mInitialized = false; 96 private boolean mUserSpecificInfoInitialized = false; 97 private final Object mSetupLock = new Object(); 98 99 // The main data structure that holds on to the {profile:list of known and connectible devices} 100 private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap; 101 102 /// TODO(vnori): fix this. b/70029056 103 private static final int NUM_SUPPORTED_PHONE_CONNECTIONS = 4; // num of HFP and PBAP connections 104 private static final int NUM_SUPPORTED_MSG_CONNECTIONS = 4; // num of MAP connections 105 private static final int NUM_SUPPORTED_MUSIC_CONNECTIONS = 1; // num of A2DP connections 106 private static final int NUM_SUPPORTED_NETWORK_CONNECTIONS = 1; // num of PAN connections 107 private Map<Integer, Integer> mNumSupportedActiveConnections; 108 109 private BluetoothAutoConnectStateMachine mBluetoothAutoConnectStateMachine; 110 private final BluetoothAdapter mBluetoothAdapter; 111 private BroadcastReceiver mBluetoothBroadcastReceiver; 112 113 private ICarUserService mCarUserService; 114 private PerUserCarServiceHelper mUserServiceHelper; 115 private ICarBluetoothUserService mCarBluetoothUserService; 116 private ReentrantLock mCarUserServiceAccessLock; 117 118 // Events that are listened to for triggering an auto-connect: 119 // Door unlock and ignition switch ON come from Car Property Service 120 private final CarPropertyService mCarPropertyService; 121 private final CarPropertyListener mPropertyEventListener; 122 123 // PerUserCarService related listeners 124 private final UserServiceConnectionCallback mServiceCallback; 125 126 // Car Bluetooth Priority Settings Manager 127 private final CarBluetoothService mCarBluetoothService; 128 129 // Car UX Restrictions Manager Service to know when user restrictions are in place 130 private final CarUxRestrictionsManagerService mUxRService; 131 private final CarUxRServiceListener mUxRListener; 132 // Fast Pair Provider to allow discovery of new phones 133 private final FastPairProvider mFastPairProvider; 134 135 // The Bluetooth profiles that the CarService will try to auto-connect on. 136 private final List<Integer> mProfilesToConnect; 137 private final List<Integer> mPrioritiesSupported; 138 private static final int MAX_CONNECT_RETRIES = 1; 139 private static final int PROFILE_NOT_AVAILABLE = -1; 140 141 // Device & Profile currently being connected on 142 private ConnectionParams mConnectionInFlight; 143 // Allow write to Settings.Secure 144 private boolean mAllowReadWriteToSettings = true; 145 // Maintain a list of Paired devices which haven't connected on any profiles yet. 146 private Set<BluetoothDevice> mPairedButUnconnectedDevices = new HashSet<>(); 147 148 public static BluetoothDeviceConnectionPolicy create(Context context, 149 CarPropertyService carPropertyService, PerUserCarServiceHelper userServiceHelper, 150 CarUxRestrictionsManagerService uxrService, CarBluetoothService bluetoothService) { 151 return new BluetoothDeviceConnectionPolicy(context, carPropertyService, userServiceHelper, 152 uxrService, bluetoothService); 153 } 154 155 private BluetoothDeviceConnectionPolicy(Context context, CarPropertyService carPropertyService, 156 PerUserCarServiceHelper userServiceHelper, CarUxRestrictionsManagerService uxrService, 157 CarBluetoothService bluetoothService) { 158 mContext = context; 159 mCarPropertyService = carPropertyService; 160 mUserServiceHelper = userServiceHelper; 161 mUxRService = uxrService; 162 mCarBluetoothService = bluetoothService; 163 mCarUserServiceAccessLock = new ReentrantLock(); 164 mProfilesToConnect = Arrays.asList( 165 BluetoothProfile.HEADSET_CLIENT, BluetoothProfile.A2DP_SINK, 166 BluetoothProfile.PBAP_CLIENT, BluetoothProfile.MAP_CLIENT, BluetoothProfile.PAN); 167 mPrioritiesSupported = Arrays.asList( 168 CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0, 169 CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1 170 ); 171 // mNumSupportedActiveConnections is a HashMap of mProfilesToConnect and the number of 172 // connections each profile supports currently. 173 mNumSupportedActiveConnections = new HashMap<>(mProfilesToConnect.size()); 174 for (Integer profile : mProfilesToConnect) { 175 switch (profile) { 176 case BluetoothProfile.HEADSET_CLIENT: 177 mNumSupportedActiveConnections.put(BluetoothProfile.HEADSET_CLIENT, 178 NUM_SUPPORTED_PHONE_CONNECTIONS); 179 break; 180 case BluetoothProfile.PBAP_CLIENT: 181 mNumSupportedActiveConnections.put(BluetoothProfile.PBAP_CLIENT, 182 NUM_SUPPORTED_PHONE_CONNECTIONS); 183 break; 184 case BluetoothProfile.A2DP_SINK: 185 mNumSupportedActiveConnections.put(BluetoothProfile.A2DP_SINK, 186 NUM_SUPPORTED_MUSIC_CONNECTIONS); 187 break; 188 case BluetoothProfile.MAP_CLIENT: 189 mNumSupportedActiveConnections.put(BluetoothProfile.MAP_CLIENT, 190 NUM_SUPPORTED_MSG_CONNECTIONS); 191 break; 192 case BluetoothProfile.PAN: 193 mNumSupportedActiveConnections.put(BluetoothProfile.PAN, 194 NUM_SUPPORTED_NETWORK_CONNECTIONS); 195 break; 196 } 197 } 198 199 // Listen to events for triggering auto connect 200 mPropertyEventListener = new CarPropertyListener(); 201 // Listen to UX Restrictions to know when to enable fast-pairing 202 mUxRListener = new CarUxRServiceListener(); 203 // Listen to User switching to connect to per User device. 204 mServiceCallback = new UserServiceConnectionCallback(); 205 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 206 if (mBluetoothAdapter == null) { 207 Log.w(TAG, "No Bluetooth Adapter Available"); 208 } 209 mFastPairProvider = new FastPairProvider(mContext); 210 } 211 212 /** 213 * ConnectionParams - parameters/objects relevant to the bluetooth connection calls. 214 * This encapsulates the information that is passed around across different methods in the 215 * policy. Contains the bluetooth device {@link BluetoothDevice} and the list of profiles that 216 * we want that device to connect on. 217 * Used as the currency that methods use to talk to each other in the policy. 218 */ 219 public static class ConnectionParams { 220 private BluetoothDevice mBluetoothDevice; 221 private Integer mBluetoothProfile; 222 223 public ConnectionParams() { 224 // default constructor 225 } 226 227 public ConnectionParams(Integer profile) { 228 mBluetoothProfile = profile; 229 } 230 231 public ConnectionParams(Integer profile, BluetoothDevice device) { 232 mBluetoothProfile = profile; 233 mBluetoothDevice = device; 234 } 235 236 // getters & Setters 237 public void setBluetoothDevice(BluetoothDevice device) { 238 mBluetoothDevice = device; 239 } 240 241 public void setBluetoothProfile(Integer profile) { 242 mBluetoothProfile = profile; 243 } 244 245 public BluetoothDevice getBluetoothDevice() { 246 return mBluetoothDevice; 247 } 248 249 public Integer getBluetoothProfile() { 250 return mBluetoothProfile; 251 } 252 } 253 254 /** 255 * BluetoothBroadcastReceiver receives the bluetooth related intents that are relevant to 256 * connection 257 * and bonding state changes. Reports the information to the {@link 258 * BluetoothDeviceConnectionPolicy} 259 * for it update its status. 260 */ 261 public class BluetoothBroadcastReceiver extends BroadcastReceiver { 262 @Override 263 public void onReceive(Context context, Intent intent) { 264 String action = intent.getAction(); 265 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 266 if (DBG) { 267 if (device != null) { 268 Log.d(TAG, "Received Intent for device: " + device + " " + action); 269 } else { 270 Log.d(TAG, "Received Intent no device: " + action); 271 } 272 } 273 ConnectionParams connectParams; 274 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { 275 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 276 BluetoothDevice.ERROR); 277 updateBondState(device, bondState); 278 279 } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 280 connectParams = new ConnectionParams(BluetoothProfile.A2DP_SINK, device); 281 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 282 BluetoothProfile.STATE_DISCONNECTED); 283 notifyConnectionStatus(connectParams, currState); 284 285 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 286 connectParams = new ConnectionParams(BluetoothProfile.HEADSET_CLIENT, device); 287 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 288 BluetoothProfile.STATE_DISCONNECTED); 289 notifyConnectionStatus(connectParams, currState); 290 291 } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 292 connectParams = new ConnectionParams(BluetoothProfile.PAN, device); 293 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 294 BluetoothProfile.STATE_DISCONNECTED); 295 notifyConnectionStatus(connectParams, currState); 296 297 } else if (BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 298 connectParams = new ConnectionParams(BluetoothProfile.PBAP_CLIENT, device); 299 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 300 BluetoothProfile.STATE_DISCONNECTED); 301 notifyConnectionStatus(connectParams, currState); 302 303 } else if (BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 304 connectParams = new ConnectionParams(BluetoothProfile.MAP_CLIENT, device); 305 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 306 BluetoothProfile.STATE_DISCONNECTED); 307 notifyConnectionStatus(connectParams, currState); 308 309 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 310 int currState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 311 if (DBG) { 312 Log.d(TAG, "Bluetooth Adapter State: " + currState); 313 } 314 if (currState == BluetoothAdapter.STATE_ON) { 315 // Read from Settings which devices to connect to and populate 316 // mProfileToConnectableDevicesMap 317 readAndRebuildDeviceMapFromSettings(); 318 initiateConnection(); 319 } else if (currState == BluetoothAdapter.STATE_OFF) { 320 // Write currently connected device snapshot to file. 321 writeDeviceInfoToSettings(); 322 resetBluetoothDevicesConnectionInfo(); 323 } 324 } else if (BluetoothDevice.ACTION_UUID.equals(action)) { 325 // Received during pairing with the UUIDs of the Bluetooth profiles supported by 326 // the remote device. 327 if (DBG) { 328 Log.d(TAG, "Received UUID intent for device " + device); 329 } 330 Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); 331 if (uuids != null) { 332 ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length]; 333 for (int i = 0; i < uuidsToSend.length; i++) { 334 uuidsToSend[i] = (ParcelUuid) uuids[i]; 335 } 336 setProfilePriorities(device, uuidsToSend, BluetoothProfile.PRIORITY_ON); 337 } 338 339 } 340 } 341 } 342 343 /** 344 * Set priority for the Bluetooth profiles. 345 * 346 * The Bluetooth service stores the priority of a Bluetooth profile per device as a key value 347 * pair - BluetoothProfile_device:<Priority>. 348 * When we pair a device from the Settings App, the expected behavior is for the app to connect 349 * on all appropriate profiles after successful pairing automatically, without the user having 350 * to explicitly issue a connect. The settings app checks for the priority of the device from 351 * the above key-value pair and if the priority is set to PRIORITY_OFF or PRIORITY_UNDEFINED, 352 * the settings app will stop with just pairing and not connect. 353 * This scenario will happen when we pair a device, then unpair it and then pair it again. When 354 * the device is unpaired, the BT stack sets the priority for that device to PRIORITY_UNDEFINED 355 * ( as a way of resetting). So, the next time the same device is paired, the Settings app will 356 * stop with just pairing and not connect as explained above. Here, we register to receive the 357 * ACTION_UUID intent, which will broadcast the UUIDs corresponding to the profiles supported by 358 * the remote device which is successfully paired and we turn on the priority so when the 359 * Settings app tries to check before connecting, the priority is set to the expected value. 360 * 361 * @param device - Remote Bluetooth device 362 * @param uuids - UUIDs of the Bluetooth Profiles supported by the remote device 363 * @param priority - priority to set 364 */ 365 private void setProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids, int priority) { 366 // need the BluetoothProfile proxy to be able to call the setPriority API 367 if (mCarBluetoothUserService == null) { 368 mCarBluetoothUserService = setupBluetoothUserService(); 369 } 370 if (mCarBluetoothUserService != null) { 371 for (Integer profile : mProfilesToConnect) { 372 setBluetoothProfilePriorityIfUuidFound(uuids, profile, device, priority); 373 } 374 } 375 } 376 377 private void setBluetoothProfilePriorityIfUuidFound(ParcelUuid[] uuids, int profile, 378 BluetoothDevice device, int priority) { 379 if (mCarBluetoothUserService == null || device == null) { 380 return; 381 } 382 // Build a list of UUIDs that represent a profile. 383 List<ParcelUuid> uuidsToCheck = new ArrayList<>(); 384 switch (profile) { 385 case BluetoothProfile.A2DP_SINK: 386 uuidsToCheck.add(BluetoothUuid.AudioSource); 387 break; 388 case BluetoothProfile.HEADSET_CLIENT: 389 uuidsToCheck.add(BluetoothUuid.Handsfree_AG); 390 uuidsToCheck.add(BluetoothUuid.HSP_AG); 391 break; 392 case BluetoothProfile.PBAP_CLIENT: 393 uuidsToCheck.add(BluetoothUuid.PBAP_PSE); 394 break; 395 case BluetoothProfile.MAP_CLIENT: 396 uuidsToCheck.add(BluetoothUuid.MAS); 397 break; 398 case BluetoothProfile.PAN: 399 uuidsToCheck.add(BluetoothUuid.PANU); 400 break; 401 } 402 403 for (ParcelUuid uuid : uuidsToCheck) { 404 if (BluetoothUuid.isUuidPresent(uuids, uuid)) { 405 try { 406 mCarBluetoothUserService.setProfilePriority(profile, device, priority); 407 } catch (RemoteException e) { 408 Log.e(TAG, "RemoteException calling setProfilePriority"); 409 } 410 // if any one of the uuid in uuidsTocheck is present, set the priority and break 411 break; 412 } 413 } 414 } 415 416 /** 417 * Cleanup state and reinitialize whenever we connect to the PerUserCarService. 418 * This happens in init() and whenever the PerUserCarService is restarted on User Switch Events 419 */ 420 @VisibleForTesting 421 class UserServiceConnectionCallback implements PerUserCarServiceHelper.ServiceCallback { 422 @Override 423 public void onServiceConnected(ICarUserService carUserService) { 424 if (mCarUserServiceAccessLock != null) { 425 mCarUserServiceAccessLock.lock(); 426 try { 427 mCarUserService = carUserService; 428 } finally { 429 mCarUserServiceAccessLock.unlock(); 430 } 431 } 432 if (DBG) { 433 Log.d(TAG, "Connected to PerUserCarService"); 434 } 435 // Get the BluetoothUserService and also setup the Bluetooth Connection Proxy for 436 // all profiles. 437 mCarBluetoothUserService = setupBluetoothUserService(); 438 // re-initialize for current user. 439 initializeUserSpecificInfo(); 440 } 441 442 @Override 443 public void onPreUnbind() { 444 if (DBG) { 445 Log.d(TAG, "Before Unbinding from UserService"); 446 } 447 try { 448 if (mCarBluetoothUserService != null) { 449 mCarBluetoothUserService.closeBluetoothConnectionProxy(); 450 } 451 } catch (RemoteException e) { 452 Log.e(TAG, 453 "Remote Exception during closeBluetoothConnectionProxy(): " 454 + e.getMessage()); 455 } 456 // Clean up information related to user who went background. 457 cleanupUserSpecificInfo(); 458 } 459 460 @Override 461 public void onServiceDisconnected() { 462 if (DBG) { 463 Log.d(TAG, "Disconnected from PerUserCarService"); 464 } 465 if (mCarUserServiceAccessLock != null) { 466 mCarUserServiceAccessLock.lock(); 467 try { 468 mCarBluetoothUserService = null; 469 mCarUserService = null; 470 } finally { 471 mCarUserServiceAccessLock.unlock(); 472 } 473 } 474 } 475 } 476 477 /** 478 * Gets the Per User Car Bluetooth Service (ICarBluetoothService) from the PerUserCarService 479 * which acts as a top level Service running in the current user context. 480 * Also sets up the connection proxy objects required to communicate with the Bluetooth 481 * Profile Services. 482 * 483 * @return ICarBluetoothUserService running in current user 484 */ 485 private ICarBluetoothUserService setupBluetoothUserService() { 486 ICarBluetoothUserService carBluetoothUserService = null; 487 if (mCarUserService != null) { 488 try { 489 carBluetoothUserService = mCarUserService.getBluetoothUserService(); 490 if (carBluetoothUserService != null) { 491 if (DBG) { 492 Log.d(TAG, "Got CarBTUsrSvc"); 493 } 494 carBluetoothUserService.setupBluetoothConnectionProxy(); 495 } 496 } catch (RemoteException e) { 497 Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: " 498 + e.getMessage()); 499 } 500 } else { 501 if (DBG) { 502 Log.d(TAG, "PerUserCarService not connected"); 503 } 504 } 505 return carBluetoothUserService; 506 } 507 508 /** 509 * Setup the Bluetooth profile service connections and Vehicle Event listeners. 510 * and start the state machine -{@link BluetoothAutoConnectStateMachine} 511 */ 512 public synchronized void init() { 513 if (DBG) { 514 Log.d(TAG, "init()"); 515 } 516 // Initialize information specific to current user. 517 initializeUserSpecificInfo(); 518 // Listen to various events coming from the vehicle. 519 setupEventListenersLocked(); 520 mInitialized = true; 521 } 522 523 /** 524 * Setup and initialize information that is specific per User account, which involves: 525 * 1. Reading the list of devices to connect for current user and initialize the deviceMap 526 * with that information. 527 * 2. Register a BroadcastReceiver for bluetooth related events for the current user. 528 * 3. Start and bind to {@link PerUserCarService} as current user. 529 * 4. Start the {@link BluetoothAutoConnectStateMachine} 530 */ 531 private void initializeUserSpecificInfo() { 532 synchronized (mSetupLock) { 533 if (DBG) { 534 Log.d(TAG, "initializeUserSpecificInfo()"); 535 } 536 if (mUserSpecificInfoInitialized) { 537 if (DBG) { 538 Log.d(TAG, "Already Initialized"); 539 } 540 return; 541 } 542 mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this); 543 readAndRebuildDeviceMapFromSettings(); 544 setupBluetoothEventsIntentFilterLocked(); 545 546 mConnectionInFlight = new ConnectionParams(); 547 mUserSpecificInfoInitialized = true; 548 } 549 } 550 551 /** 552 * Setting up the Intent filter for Bluetooth related broadcasts 553 * This includes knowing when the 554 * 1. Bluetooth Adapter turned on/off 555 * 2. Bonding State of a device changes 556 * 3. A specific profile's connection state changes. 557 */ 558 private void setupBluetoothEventsIntentFilterLocked() { 559 mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver(); 560 IntentFilter profileFilter = new IntentFilter(); 561 profileFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 562 profileFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 563 profileFilter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 564 profileFilter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 565 profileFilter.addAction(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED); 566 profileFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); 567 profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 568 profileFilter.addAction(BluetoothDevice.ACTION_UUID); 569 if (mContext != null) { 570 mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT, 571 profileFilter, null, null); 572 } 573 } 574 575 /** 576 * Initialize the {@link #mProfileToConnectableDevicesMap}. 577 * {@link #mProfileToConnectableDevicesMap} stores the profile:DeviceList information. This 578 * method retrieves it from persistent memory. 579 */ 580 private synchronized void initDeviceMap() { 581 if (mProfileToConnectableDevicesMap == null) { 582 mProfileToConnectableDevicesMap = new HashMap<>(); 583 for (Integer profile : mProfilesToConnect) { 584 // Build the BluetoothDevicesInfo for this profile. 585 BluetoothDevicesInfo devicesInfo = new BluetoothDevicesInfo(profile, 586 mNumSupportedActiveConnections.get(profile)); 587 mProfileToConnectableDevicesMap.put(profile, devicesInfo); 588 } 589 if (DBG) { 590 Log.d(TAG, "Created a new empty Device Map"); 591 } 592 } 593 } 594 595 /** 596 * Setting up Listeners to the various events we are interested in listening to for initiating 597 * Bluetooth connection attempts. 598 */ 599 private void setupEventListenersLocked() { 600 // Setting up a listener for events from CarPropertyService 601 // For now, we listen to door unlock signal and Ignition state START coming from 602 // {@link CarPropertyService} 603 mCarPropertyService.registerListener(VehicleProperty.DOOR_LOCK, 0, mPropertyEventListener); 604 mCarPropertyService.registerListener(VehicleProperty.IGNITION_STATE, 0, 605 mPropertyEventListener); 606 // Get Current restrictions and handle them 607 handleUxRestrictionsChanged(mUxRService.getCurrentUxRestrictions()); 608 // Register for future changes to the UxRestrictions 609 mUxRService.registerUxRestrictionsChangeListener(mUxRListener); 610 mUserServiceHelper.registerServiceCallback(mServiceCallback); 611 } 612 613 /** 614 * Handles events coming in from the {@link CarPropertyService} 615 * The events that can trigger Bluetooth Scanning from CarPropertyService are Door Unlock and 616 * Igntion START. Upon an event of interest, initiate a connection attempt by calling 617 * the policy {@link BluetoothDeviceConnectionPolicy} 618 */ 619 @VisibleForTesting 620 class CarPropertyListener extends ICarPropertyEventListener.Stub { 621 @Override 622 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 623 for (CarPropertyEvent event : events) { 624 if (DBG) { 625 Log.d(TAG, "Cabin change Event : " + event.getEventType()); 626 } 627 CarPropertyValue value = event.getCarPropertyValue(); 628 Object o = value.getValue(); 629 630 switch (value.getPropertyId()) { 631 case VehicleProperty.DOOR_LOCK: 632 if (o instanceof Boolean) { 633 Boolean locked = (Boolean) o; 634 if (DBG) { 635 Log.d(TAG, "Door Lock: " + locked); 636 } 637 // Attempting a connection only on a door unlock 638 if (!locked) { 639 initiateConnection(); 640 } 641 } 642 break; 643 case VehicleProperty.IGNITION_STATE: 644 if (o instanceof Integer) { 645 Integer state = (Integer) o; 646 if (DBG) { 647 Log.d(TAG, "Sensor value : " + state); 648 } 649 // Attempting a connection only on IgntionState START 650 if (state == VehicleIgnitionState.START) { 651 initiateConnection(); 652 } 653 } 654 break; 655 } 656 } 657 } 658 } 659 660 private class CarUxRServiceListener extends ICarUxRestrictionsChangeListener.Stub { 661 @Override 662 public void onUxRestrictionsChanged(CarUxRestrictions restrictions) { 663 handleUxRestrictionsChanged(restrictions); 664 } 665 } 666 667 private void handleUxRestrictionsChanged(CarUxRestrictions restrictions) { 668 if (restrictions == null) { 669 return; 670 } 671 if ((restrictions.getActiveRestrictions() & CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP) 672 == CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP) { 673 mFastPairProvider.stopAdvertising(); 674 } else { 675 mFastPairProvider.startAdvertising(); 676 } 677 } 678 679 /** 680 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine - 681 * {@link BluetoothAutoConnectStateMachine} 682 */ 683 public synchronized void release() { 684 if (DBG) { 685 Log.d(TAG, "release()"); 686 } 687 mInitialized = false; 688 writeDeviceInfoToSettings(); 689 cleanupUserSpecificInfo(); 690 closeEventListeners(); 691 } 692 693 /** 694 * Clean up information related to user who went background. 695 */ 696 private void cleanupUserSpecificInfo() { 697 synchronized (mSetupLock) { 698 if (DBG) { 699 Log.d(TAG, "cleanupUserSpecificInfo()"); 700 } 701 if (!mUserSpecificInfoInitialized) { 702 if (DBG) { 703 Log.d(TAG, "User specific Info Not initialized..Not cleaning up"); 704 } 705 return; 706 } 707 mUserSpecificInfoInitialized = false; 708 // quit the state machine 709 mBluetoothAutoConnectStateMachine.doQuit(); 710 mProfileToConnectableDevicesMap = null; 711 mConnectionInFlight = null; 712 if (mBluetoothBroadcastReceiver != null) { 713 if (mContext != null) { 714 mContext.unregisterReceiver(mBluetoothBroadcastReceiver); 715 } 716 mBluetoothBroadcastReceiver = null; 717 } 718 } 719 } 720 721 /** 722 * Unregister the listeners to the various Vehicle events coming from other parts of the 723 * CarService 724 */ 725 private void closeEventListeners() { 726 if (DBG) { 727 Log.d(TAG, "closeEventListeners()"); 728 } 729 mCarPropertyService.unregisterListener(VehicleProperty.DOOR_LOCK, mPropertyEventListener); 730 mCarPropertyService.unregisterListener(VehicleProperty.IGNITION_STATE, 731 mPropertyEventListener); 732 mUserServiceHelper.unregisterServiceCallback(mServiceCallback); 733 } 734 735 /** 736 * Resets the {@link BluetoothDevicesInfo#mConnectionInfo} of all the profiles to start from 737 * a clean slate. The ConnectionInfo has all the book keeping information regarding the state 738 * of connection attempts - like which device in the device list for the profile is the next 739 * to try connecting etc. 740 * This method does not clear the {@link BluetoothDevicesInfo#mDeviceInfoList} like the {@link 741 * #resetProfileToConnectableDevicesMap()} method does. 742 */ 743 private synchronized void resetBluetoothDevicesConnectionInfo() { 744 if (DBG) { 745 Log.d(TAG, "Resetting ConnectionInfo for all profiles"); 746 } 747 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) { 748 devInfo.resetConnectionInfoLocked(); 749 } 750 } 751 752 @VisibleForTesting 753 BroadcastReceiver getBluetoothBroadcastReceiver() { 754 return mBluetoothBroadcastReceiver; 755 } 756 757 @VisibleForTesting 758 UserServiceConnectionCallback getServiceCallback() { 759 return mServiceCallback; 760 } 761 762 @VisibleForTesting 763 CarPropertyListener getCarPropertyListener() { 764 return mPropertyEventListener; 765 } 766 767 @VisibleForTesting 768 synchronized void setAllowReadWriteToSettings(boolean allowWrite) { 769 mAllowReadWriteToSettings = allowWrite; 770 } 771 772 @VisibleForTesting 773 BluetoothDevicesInfo getBluetoothDevicesInfo(int profile) { 774 return mProfileToConnectableDevicesMap.get(profile); 775 } 776 777 @VisibleForTesting 778 String toDebugString() { 779 StringBuilder buf = new StringBuilder(); 780 for (Integer profile : mProfileToConnectableDevicesMap.keySet()) { 781 BluetoothDevicesInfo info = mProfileToConnectableDevicesMap.get(profile); 782 buf.append(" \n**** profile = " + profile); 783 buf.append(", " + info.toDebugString()); 784 } 785 return buf.toString(); 786 } 787 788 /** 789 * Resets the {@link #mProfileToConnectableDevicesMap} to a clean and empty slate. 790 */ 791 public synchronized void resetProfileToConnectableDevicesMap() { 792 if (DBG) { 793 Log.d(TAG, "Resetting the mProfilesToConnectableDevicesMap"); 794 } 795 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) { 796 devInfo.resetDeviceListLocked(); 797 } 798 } 799 800 /** 801 * Returns the list of profiles that the Autoconnection policy attempts to connect on 802 * 803 * @return profile list. 804 */ 805 public synchronized List<Integer> getProfilesToConnect() { 806 return mProfilesToConnect; 807 } 808 809 /** 810 * Add a new Profile to the list of To Be Connected profiles. 811 * 812 * @param profile - ProfileInfo of the new profile to be added. 813 */ 814 public synchronized void addProfile(Integer profile) { 815 mProfilesToConnect.add(profile); 816 } 817 818 /** 819 * Add or remove a device based on the bonding state change. 820 * 821 * @param device - device to add/remove 822 * @param bondState - current bonding state 823 */ 824 private void updateBondState(BluetoothDevice device, int bondState) { 825 if (device == null) { 826 Log.e(TAG, "updateBondState: device Null"); 827 return; 828 } 829 if (DBG) { 830 Log.d(TAG, "BondState :" + bondState + " Device: " + device); 831 } 832 // Bonded devices are added to a profile's device list after the device CONNECTS on the 833 // profile. When unpaired, we remove the device from all of the profiles' device list. 834 if (bondState == BluetoothDevice.BOND_NONE) { 835 for (Integer profile : mProfilesToConnect) { 836 if (DBG) { 837 Log.d(TAG, "Removing " + device + " from profile: " + profile); 838 } 839 removeDeviceFromProfile(device, profile); 840 } 841 } else if (bondState == BluetoothDevice.BOND_BONDED) { 842 // A device just paired. When it connects on HFP profile, then immediately initiate 843 // connections on PBAP and MAP. for now, maintain this fact in a data structure 844 // to be looked up when HFP profile connects. 845 mPairedButUnconnectedDevices.add(device); 846 } 847 } 848 849 /** 850 * Add a new device to the list of devices connect-able on the given profile 851 * 852 * @param device - Bluetooth device to be added 853 * @param profile - profile to add the device to. 854 */ 855 private synchronized void addDeviceToProfile(BluetoothDevice device, Integer profile) { 856 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 857 if (devInfo == null) { 858 if (DBG) { 859 Log.d(TAG, "Creating devInfo for profile: " + profile); 860 } 861 devInfo = new BluetoothDevicesInfo(profile); 862 mProfileToConnectableDevicesMap.put(profile, devInfo); 863 } 864 devInfo.addDeviceLocked(device); 865 } 866 867 /** 868 * Remove the device from the list of devices connect-able on the gievn profile. 869 * 870 * @param device - Bluetooth device to be removed 871 * @param profile - profile to remove the device from 872 */ 873 private synchronized void removeDeviceFromProfile(BluetoothDevice device, Integer profile) { 874 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 875 if (devInfo != null) { 876 devInfo.removeDeviceLocked(device); 877 } 878 } 879 880 /** 881 * Initiate a bluetooth connection. 882 */ 883 private void initiateConnection() { 884 // Make sure the bluetooth adapter is available & enabled. 885 if (mBluetoothAdapter == null) { 886 Log.w(TAG, "Bluetooth Adapter null"); 887 return; 888 } 889 if (mBluetoothAdapter.isEnabled()) { 890 if (isDeviceMapEmpty()) { 891 if (DBG) { 892 Log.d(TAG, "Device Map is empty. Nothing to connect to"); 893 } 894 return; 895 } 896 resetDeviceAvailableToConnect(); 897 if (DBG) { 898 Log.d(TAG, "initiateConnection() Reset Device Availability"); 899 } 900 mBluetoothAutoConnectStateMachine.sendMessage(BluetoothAutoConnectStateMachine 901 .CONNECT); 902 } else { 903 if (DBG) { 904 Log.d(TAG, "Bluetooth Adapter not enabled."); 905 } 906 } 907 } 908 909 /** 910 * Find an unconnected profile and find a device to connect on it. 911 * Finds the appropriate device for the profile from the information available in 912 * {@link #mProfileToConnectableDevicesMap} 913 * 914 * @return true - if we found a device to connect on for any of the {@link #mProfilesToConnect} 915 * false - if we cannot find a device to connect to or if we are not ready to connect yet. 916 */ 917 public synchronized boolean findDeviceToConnect() { 918 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled() 919 || mProfileToConnectableDevicesMap == null || !mInitialized) { 920 if (DBG) { 921 if (mProfileToConnectableDevicesMap == null) { 922 Log.d(TAG, "findDeviceToConnect(): Device Map null"); 923 } else { 924 Log.d(TAG, "findDeviceToConnect(): BT Adapter not enabled"); 925 } 926 } 927 return false; 928 } 929 boolean connectingToADevice = false; 930 // Get the first unconnected profile that we can try to make a connection 931 Integer nextProfile = getNextProfileToConnectLocked(); 932 // Keep going through the profiles until we find a device that we can connect to 933 while (nextProfile != PROFILE_NOT_AVAILABLE) { 934 if (DBG) { 935 Log.d(TAG, "connectToProfile(): " + nextProfile); 936 } 937 // find a device that is next in line for a connection attempt for that profile 938 // and try connecting to it. 939 connectingToADevice = connectToNextDeviceInQueueLocked(nextProfile); 940 // If we found a device to connect, break out of the loop 941 if (connectingToADevice) { 942 if (DBG) { 943 Log.d(TAG, "Found device to connect to"); 944 } 945 BluetoothDeviceConnectionPolicy.ConnectionParams btParams = 946 new BluetoothDeviceConnectionPolicy.ConnectionParams( 947 mConnectionInFlight.getBluetoothProfile(), 948 mConnectionInFlight.getBluetoothDevice()); 949 // set up a time out 950 mBluetoothAutoConnectStateMachine.sendMessageDelayed( 951 BluetoothAutoConnectStateMachine.CONNECT_TIMEOUT, btParams, 952 BluetoothAutoConnectStateMachine.CONNECTION_TIMEOUT_MS); 953 break; 954 } else { 955 // result will be false, if there are no more devices to connect 956 // or if the ProfileProxy objects are null (ServiceConnection 957 // not yet established for this profile) 958 if (DBG) { 959 Log.d(TAG, "No more device to connect on Profile: " + nextProfile); 960 } 961 nextProfile = getNextProfileToConnectLocked(); 962 } 963 } 964 return connectingToADevice; 965 } 966 967 /** 968 * Get the first unconnected profile. 969 * 970 * @return profile to connect. 971 * Special return value 0 if 972 * 1. all profiles have been connected on. 973 * 2. no profile connected but no nearby known device that can be connected to 974 */ 975 private Integer getNextProfileToConnectLocked() { 976 for (Integer profile : mProfilesToConnect) { 977 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 978 if (devInfo != null) { 979 if (devInfo.isProfileConnectableLocked()) { 980 return profile; 981 } 982 } else { 983 Log.e(TAG, "Unexpected: devInfo null for profile: " + profile); 984 } 985 } 986 // Reaching here denotes all profiles are connected or No devices available for any profile 987 if (DBG) { 988 Log.d(TAG, "No disconnected profiles"); 989 } 990 return PROFILE_NOT_AVAILABLE; 991 } 992 993 /** 994 * Checks if the Bluetooth profile service's proxy object is available. 995 * Proxy obj should be available before we can attempt to connect on that profile. 996 * 997 * @param profile The profile to check the presence of the proxy object 998 * @return True if the proxy obj exists. False otherwise. 999 */ 1000 private boolean isProxyAvailable(Integer profile) { 1001 if (mCarBluetoothUserService == null) { 1002 mCarBluetoothUserService = setupBluetoothUserService(); 1003 if (mCarBluetoothUserService == null) { 1004 Log.d(TAG, "CarBluetoothUserSvc null. Car service not bound to PerUserCarSvc."); 1005 return false; 1006 } 1007 } 1008 try { 1009 if (!mCarBluetoothUserService.isBluetoothConnectionProxyAvailable(profile)) { 1010 // proxy unavailable. 1011 if (DBG) { 1012 Log.d(TAG, "Proxy for Bluetooth Profile Service Unavailable: " + profile); 1013 } 1014 return false; 1015 } 1016 } catch (RemoteException e) { 1017 Log.e(TAG, "Car BT Service Remote Exception."); 1018 return false; 1019 } 1020 return true; 1021 } 1022 1023 /** 1024 * Creates a device connection for the given profile. 1025 * 1026 * @param profile The profile on which the connection is being setup 1027 * @param device The device for which the connection is to be made. 1028 * @return true if the connection is setup successfully. False otherwise. 1029 */ 1030 boolean connectToDeviceOnProfile(Integer profile, BluetoothDevice device) { 1031 if (DBG) { 1032 Log.d(TAG, "in connectToDeviceOnProfile for Profile:" + profile + 1033 ", device: " + Utils.getDeviceDebugInfo(device)); 1034 } 1035 if (!isProxyAvailable(profile)) { 1036 if (DBG) { 1037 Log.d(TAG, "No proxy available for Profile: " + profile); 1038 } 1039 return false; 1040 } 1041 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 1042 if (devInfo == null) { 1043 if (DBG) { 1044 Log.d(TAG, "devInfo NULL on Profile:" + profile); 1045 } 1046 return false; 1047 } 1048 int state = devInfo.getCurrentConnectionStateLocked(device); 1049 if (state == BluetoothProfile.STATE_CONNECTED || 1050 state == BluetoothProfile.STATE_CONNECTING) { 1051 if (DBG) { 1052 Log.d(TAG, "device " + Utils.getDeviceDebugInfo(device) + 1053 " is already connected/connecting on Profile:" + profile); 1054 } 1055 return true; 1056 } 1057 if (!devInfo.isProfileConnectableLocked()) { 1058 if (DBG) { 1059 Log.d(TAG, "isProfileConnectableLocked FALSE on Profile:" + profile + 1060 " this means number of connections on this profile max'ed out or " + 1061 " no more devices available to connect on this profile"); 1062 } 1063 return false; 1064 } 1065 try { 1066 if (mCarBluetoothUserService != null) { 1067 mCarBluetoothUserService.bluetoothConnectToProfile((int) profile, device); 1068 devInfo.setConnectionStateLocked(device, BluetoothProfile.STATE_CONNECTING); 1069 // Increment the retry count & cache what is being connected to 1070 // This method is already called from a synchronized context. 1071 mConnectionInFlight.setBluetoothDevice(device); 1072 mConnectionInFlight.setBluetoothProfile(profile); 1073 devInfo.incrementRetryCountLocked(); 1074 if (DBG) { 1075 Log.d(TAG, "Increment Retry to: " + devInfo.getRetryCountLocked() + 1076 ", for Profile: " + profile + ", on device: " + 1077 Utils.getDeviceDebugInfo(device)); 1078 } 1079 return true; 1080 } else { 1081 Log.e(TAG, "CarBluetoothUserSvc null"); 1082 } 1083 } catch (RemoteException e) { 1084 Log.e(TAG, "Remote User Service stopped responding: " + e.getMessage()); 1085 } 1086 return false; 1087 } 1088 1089 /** 1090 * Helper method to reset info in {@link #mConnectionInFlight} in the given 1091 * {@link BluetoothDevicesInfo} object. 1092 * 1093 * @param devInfo the {@link BluetoothDevicesInfo} where the info is to be reset. 1094 */ 1095 private void setProfileOnDeviceToUnavailable(BluetoothDevicesInfo devInfo) { 1096 mConnectionInFlight.setBluetoothProfile(0); 1097 mConnectionInFlight.setBluetoothDevice(null); 1098 devInfo.setDeviceAvailableToConnectLocked(false); 1099 } 1100 1101 boolean doesDeviceExistForProfile(Integer profile, BluetoothDevice device) { 1102 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 1103 if (devInfo == null) { 1104 if (DBG) { 1105 Log.d(TAG, "devInfo NULL on Profile:" + profile); 1106 } 1107 return false; 1108 } 1109 boolean b = devInfo.checkDeviceInListLocked(device); 1110 if (DBG) { 1111 Log.d(TAG, "Is device " + Utils.getDeviceDebugInfo(device) + 1112 " listed for profile " + profile + ": " + b); 1113 } 1114 return b; 1115 } 1116 1117 /** 1118 * Try to connect to the next device in the device list for the given profile. 1119 * 1120 * @param profile - profile to connect on 1121 * @return - true if we found a device to connect on for this profile 1122 * false - if we cannot find a device to connect to. 1123 */ 1124 private boolean connectToNextDeviceInQueueLocked(Integer profile) { 1125 // Get the Device Information for the given profile and find the next device to connect on 1126 BluetoothDevice devToConnect = null; 1127 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 1128 if (devInfo == null) { 1129 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile); 1130 return false; 1131 } 1132 1133 if (isProxyAvailable(profile)) { 1134 // Get the next device in the device list for this profile. 1135 devToConnect = devInfo.getNextDeviceInQueueLocked(); 1136 if (devToConnect != null) { 1137 // deviceAvailable && proxyAvailable 1138 if (connectToDeviceOnProfile(profile, devToConnect)) return true; 1139 } else { 1140 // device unavailable 1141 if (DBG) { 1142 Log.d(TAG, "No paired nearby device to connect to for profile: " + profile); 1143 } 1144 } 1145 } 1146 1147 // reset the mConnectionInFlight 1148 setProfileOnDeviceToUnavailable(devInfo); 1149 return false; 1150 } 1151 1152 /** 1153 * Update the device connection status for a profile and also notify the state machine. 1154 * This gets called from {@link BluetoothBroadcastReceiver} when it receives a Profile's 1155 * CONNECTION_STATE_CHANGED intent. 1156 * 1157 * @param params - {@link ConnectionParams} device and profile list info 1158 * @param currentState - connection result to update 1159 */ 1160 private void notifyConnectionStatus(ConnectionParams params, int currentState) { 1161 // Update the profile's BluetoothDevicesInfo. 1162 boolean isConnected; 1163 switch (currentState) { 1164 case BluetoothProfile.STATE_DISCONNECTED: { 1165 isConnected = false; 1166 break; 1167 } 1168 1169 case BluetoothProfile.STATE_CONNECTED: { 1170 isConnected = true; 1171 break; 1172 } 1173 1174 default: { 1175 if (DBG) { 1176 Log.d(TAG, "notifyConnectionStatus() Ignoring state: " + currentState); 1177 } 1178 return; 1179 } 1180 1181 } 1182 1183 boolean updateSuccessful = updateDeviceConnectionStatus(params, isConnected); 1184 if (updateSuccessful) { 1185 if (isConnected) { 1186 mBluetoothAutoConnectStateMachine.sendMessage( 1187 BluetoothAutoConnectStateMachine.DEVICE_CONNECTED, 1188 params); 1189 } else { 1190 mBluetoothAutoConnectStateMachine.sendMessage( 1191 BluetoothAutoConnectStateMachine.DEVICE_DISCONNECTED, 1192 params); 1193 } 1194 } 1195 } 1196 1197 /** 1198 * Update the profile's {@link BluetoothDevicesInfo} with the result of the connection 1199 * attempt. This gets called from the {@link BluetoothAutoConnectStateMachine} when the 1200 * connection attempt times out or from {@link BluetoothBroadcastReceiver} when it receives 1201 * a Profile's CONNECTION_STATE_CHANGED intent. 1202 * 1203 * @param params - {@link ConnectionParams} device and profile list info 1204 * @param didConnect - connection result to update 1205 */ 1206 public synchronized boolean updateDeviceConnectionStatus(ConnectionParams params, 1207 boolean didConnect) { 1208 if (params == null || params.getBluetoothDevice() == null) { 1209 Log.e(TAG, "updateDeviceConnectionStatus: null params"); 1210 return false; 1211 } 1212 // Get the profile to update 1213 Integer profileToUpdate = params.getBluetoothProfile(); 1214 BluetoothDevice deviceThatConnected = params.getBluetoothDevice(); 1215 if (DBG) { 1216 Log.d(TAG, "Profile: " + profileToUpdate + " Connected: " + didConnect + " on " 1217 + deviceThatConnected); 1218 } 1219 1220 // If the device just connected to HEADSET_CLIENT profile, initiate 1221 // connections on PBAP & MAP profiles but let that begin after a timeout period. 1222 // timeout allows A2DP profile to complete its connection, so that there is no race 1223 // condition between 1224 // Phone trying to connect on A2DP 1225 // and, Car trying to connect on PBAP & MAP. 1226 if (didConnect && profileToUpdate == BluetoothProfile.HEADSET_CLIENT) { 1227 // Unlock the profiles PBAP, MAP in BluetoothDevicesInfo, so that they can be 1228 // connected on. 1229 for (Integer profile : Arrays.asList(BluetoothProfile.PBAP_CLIENT, 1230 BluetoothProfile.MAP_CLIENT)) { 1231 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 1232 if (devInfo == null) { 1233 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile); 1234 return false; 1235 } 1236 devInfo.setDeviceAvailableToConnectLocked(true); 1237 } 1238 if (DBG) { 1239 Log.d(TAG, "connect to PBAP/MAP after disconnect: "); 1240 } 1241 mBluetoothAutoConnectStateMachine.sendMessageDelayed( 1242 BluetoothAutoConnectStateMachine.CHECK_CLIENT_PROFILES, params, 1243 BluetoothAutoConnectStateMachine.CONNECT_MORE_PROFILES_TIMEOUT_MS); 1244 } 1245 1246 // If the connection update is on a different profile or device (a very rare possibility), 1247 // it is handled automatically. Just logging it here. 1248 if (DBG) { 1249 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothProfile() != null) { 1250 if (profileToUpdate.equals(mConnectionInFlight.getBluetoothProfile()) == false) { 1251 Log.d(TAG, "Updating profile " + profileToUpdate 1252 + " different from connection in flight " 1253 + mConnectionInFlight.getBluetoothProfile()); 1254 } 1255 } 1256 1257 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothDevice() != null) { 1258 if (deviceThatConnected.equals(mConnectionInFlight.getBluetoothDevice()) == false) { 1259 Log.d(TAG, "Updating device: " + deviceThatConnected 1260 + " different from connection in flight: " 1261 + mConnectionInFlight.getBluetoothDevice()); 1262 1263 } 1264 } 1265 } 1266 BluetoothDevicesInfo devInfo = null; 1267 devInfo = mProfileToConnectableDevicesMap.get(profileToUpdate); 1268 if (devInfo == null) { 1269 Log.e(TAG, "Unexpected: devInfo null for profile: " + profileToUpdate); 1270 return false; 1271 } 1272 1273 boolean retry = canRetryConnection(profileToUpdate); 1274 // Update the status and also if a retry attempt can be made if the 1275 // connection timed out in the previous attempt. 1276 if (DBG) { 1277 Log.d(TAG, "Retry? : " + retry); 1278 } 1279 devInfo.updateConnectionStatusLocked(deviceThatConnected, didConnect, retry); 1280 // Write to persistent memory to have the latest snapshot available 1281 writeDeviceInfoToSettings(params); 1282 return true; 1283 } 1284 1285 /** 1286 * Returns if we can retry connection attempt on the given profile for the device that is 1287 * currently in the head of the queue. 1288 * 1289 * @param profile - Profile to check 1290 */ 1291 private synchronized boolean canRetryConnection(Integer profile) { 1292 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 1293 if (devInfo == null) { 1294 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile); 1295 return false; 1296 } 1297 if (devInfo.getRetryCountLocked() < MAX_CONNECT_RETRIES) { 1298 return true; 1299 } else { 1300 return false; 1301 } 1302 } 1303 1304 /** 1305 * Helper method to see if there are any connect-able devices on any of the 1306 * profiles. 1307 * 1308 * @return true - if {@link #mProfileToConnectableDevicesMap} does not have any devices for any 1309 * profiles. 1310 * false - if {@link #mProfileToConnectableDevicesMap} has a device for at least one profile. 1311 */ 1312 private synchronized boolean isDeviceMapEmpty() { 1313 boolean empty = true; 1314 for (Integer profile : mProfilesToConnect) { 1315 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile); 1316 if (devInfo != null) { 1317 if (devInfo.getNumberOfPairedDevicesLocked() != 0) { 1318 if (DBG) { 1319 Log.d(TAG, "Device map not empty. Profile: " + profile + " has " 1320 + devInfo.getNumberOfPairedDevicesLocked() + " paired devices"); 1321 } 1322 empty = false; 1323 break; 1324 } 1325 } 1326 } 1327 return empty; 1328 } 1329 1330 /** 1331 * Reset the Device Available to Connect information for all profiles to Available. 1332 * If in a previous connection attempt, we failed to connect on all devices for a profile, 1333 * we would update deviceAvailableToConnect for that profile to false. That information 1334 * is used to deduce if we should move to the next profile. If marked false, we will not 1335 * try to connect on that profile anymore as part of that connection attempt. 1336 * However, when we get another connection trigger from the vehicle, we need to reset the 1337 * deviceAvailableToConnect information so we can start the connection attempts all over 1338 * again. 1339 */ 1340 private synchronized void resetDeviceAvailableToConnect() { 1341 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) { 1342 devInfo.setDeviceAvailableToConnectLocked(true); 1343 devInfo.resetDeviceIndex(); 1344 } 1345 } 1346 1347 /** 1348 * Utility function - Prints the Profile: list of devices information to log 1349 * Caller should wrap a DBG around this, since this is for debugging purpose. 1350 * 1351 * @param writer - PrintWriter 1352 */ 1353 private synchronized void printDeviceMap(PrintWriter writer) { 1354 if (mProfileToConnectableDevicesMap == null) { 1355 return; 1356 } 1357 for (Integer profile : mProfileToConnectableDevicesMap.keySet()) { 1358 writer.print("Profile: " + Utils.getProfileName(profile) + "\t"); 1359 writer.print("\n" + mProfileToConnectableDevicesMap.get(profile).toDebugString()); 1360 writer.println(); 1361 } 1362 } 1363 1364 /** 1365 * Write the device list for all bluetooth profiles that connected. 1366 * 1367 * @return true if the write was successful, false otherwise 1368 */ 1369 private synchronized boolean writeDeviceInfoToSettings() { 1370 ConnectionParams params; 1371 boolean writeResult; 1372 for (Integer profile : mProfilesToConnect) { 1373 params = new ConnectionParams(profile); 1374 writeResult = writeDeviceInfoToSettings(params); 1375 if (!writeResult) { 1376 Log.e(TAG, "Error writing Device Info for profile:" + profile); 1377 return writeResult; 1378 } 1379 } 1380 return true; 1381 } 1382 1383 /** 1384 * Write information about which devices connected on which profile to Settings.Secure. 1385 * Essentially the list of devices that a profile can connect on the next auto-connect 1386 * attempt. 1387 * 1388 * @param params - ConnectionParams indicating which bluetooth profile to write this 1389 * information 1390 * for. 1391 * @return true if the write was successful, false otherwise 1392 */ 1393 public synchronized boolean writeDeviceInfoToSettings(ConnectionParams params) { 1394 if (!mAllowReadWriteToSettings) { 1395 return false; 1396 } 1397 boolean writeSuccess = true; 1398 Integer profileToUpdate = params.getBluetoothProfile(); 1399 1400 if (mProfileToConnectableDevicesMap == null) { 1401 writeSuccess = false; 1402 } else { 1403 List<String> deviceNames = new ArrayList<>(); 1404 BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profileToUpdate); 1405 StringBuilder sb = new StringBuilder(); 1406 String delimiter = ""; // start off with no delimiter. 1407 1408 // Iterate through the List<BluetoothDevice> and build a String that is 1409 // names of all devices to be connected for this profile joined together and 1410 // delimited by a delimiter (its a ',' here) 1411 if (devicesInfo != null && devicesInfo.getDeviceList() != null) { 1412 for (BluetoothDevice device : devicesInfo.getDeviceList()) { 1413 sb.append(delimiter); 1414 sb.append(device.getAddress()); 1415 delimiter = SETTINGS_DELIMITER; 1416 } 1417 1418 } 1419 // joinedDeviceNames has something like "22:22:33:44:55:AB,22:23:xx:xx:xx:xx" 1420 // mac addresses of connectable devices separated by a delimiter 1421 String joinedDeviceNames = sb.toString(); 1422 if (DBG) { 1423 Log.d(TAG, "Profile: " + profileToUpdate + " Writing: " + joinedDeviceNames); 1424 } 1425 long userId = ActivityManager.getCurrentUser(); 1426 switch (profileToUpdate) { 1427 case BluetoothProfile.A2DP_SINK: 1428 Settings.Secure.putStringForUser(mContext.getContentResolver(), 1429 KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, 1430 joinedDeviceNames, (int) userId); 1431 break; 1432 1433 case BluetoothProfile.HEADSET_CLIENT: 1434 Settings.Secure.putStringForUser(mContext.getContentResolver(), 1435 KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, 1436 joinedDeviceNames, (int) userId); 1437 break; 1438 1439 case BluetoothProfile.PBAP_CLIENT: 1440 // use the phone 1441 break; 1442 1443 case BluetoothProfile.MAP_CLIENT: 1444 Settings.Secure.putStringForUser(mContext.getContentResolver(), 1445 KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, 1446 joinedDeviceNames, (int) userId); 1447 break; 1448 case BluetoothProfile.PAN: 1449 Settings.Secure.putStringForUser(mContext.getContentResolver(), 1450 KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES, 1451 joinedDeviceNames, (int) userId); 1452 break; 1453 } 1454 } 1455 return writeSuccess; 1456 } 1457 1458 /** 1459 * Read the device information from Settings.Secure and populate the 1460 * {@link #mProfileToConnectableDevicesMap} 1461 * 1462 * Device MAC addresses are written to Settings.Secure delimited by a ','. 1463 * Ex: android.car.BLUETOOTH_AUTOCONNECT_PHONE_DEVICES: xx:xx:xx:xx:xx:xx,yy:yy:yy:yy:yy 1464 * denotes that two devices with addresses xx:xx:xx:xx:xx:xx & yy:yy:yy:yy:yy:yy were connected 1465 * as phones (in HFP and PBAP profiles) the last time this user was logged in. 1466 * 1467 * @return - true if the read was successful, false if 1. BT Adapter not enabled 2. No prior 1468 * bonded devices 3. No information stored in Settings for this user. 1469 */ 1470 public synchronized boolean readAndRebuildDeviceMapFromSettings() { 1471 List<String> deviceList; 1472 String devices = null; 1473 // Create and initialize mProfileToConnectableDevicesMap if needed. 1474 initDeviceMap(); 1475 if (mBluetoothAdapter != null) { 1476 if (DBG) { 1477 Log.d(TAG, 1478 "Number of Bonded devices:" + mBluetoothAdapter.getBondedDevices().size()); 1479 } 1480 if (mBluetoothAdapter.getBondedDevices().isEmpty()) { 1481 if (DBG) { 1482 Log.d(TAG, "No Bonded Devices available. Quit rebuilding"); 1483 } 1484 return false; 1485 } 1486 } 1487 if (!mAllowReadWriteToSettings) { 1488 return false; 1489 } 1490 // Read from Settings.Secure for the current user. There are 3 keys 1 each for Phone 1491 // (HFP & PBAP), 1 for Music (A2DP) and 1 for Messaging device (MAP) 1492 long userId = ActivityManager.getCurrentUser(); 1493 for (Integer profile : mProfilesToConnect) { 1494 switch (profile) { 1495 case BluetoothProfile.A2DP_SINK: 1496 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(), 1497 KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, (int) userId); 1498 break; 1499 case BluetoothProfile.PBAP_CLIENT: 1500 // fall through 1501 case BluetoothProfile.HEADSET_CLIENT: 1502 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(), 1503 KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, (int) userId); 1504 break; 1505 case BluetoothProfile.MAP_CLIENT: 1506 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(), 1507 KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, (int) userId); 1508 break; 1509 case BluetoothProfile.PAN: 1510 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(), 1511 KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES, (int) userId); 1512 default: 1513 Log.e(TAG, "Unexpected profile"); 1514 break; 1515 } 1516 1517 if (devices == null) { 1518 if (DBG) { 1519 Log.d(TAG, "No device information stored in Settings"); 1520 } 1521 return false; 1522 } 1523 if (DBG) { 1524 Log.d(TAG, "Devices in Settings: " + devices); 1525 } 1526 // Get a list of Device Mac Addresses from the value 1527 deviceList = Arrays.asList(devices.split(SETTINGS_DELIMITER)); 1528 if (deviceList == null) { 1529 return false; 1530 } 1531 BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile); 1532 // Do we have a bonded device with this name? If so, get it and populate the device 1533 // map. 1534 for (String address : deviceList) { 1535 BluetoothDevice deviceToAdd = getBondedDeviceWithGivenName(address); 1536 if (deviceToAdd != null) { 1537 devicesInfo.addDeviceLocked(deviceToAdd); 1538 } else { 1539 if (DBG) { 1540 Log.d(TAG, "No device with name " + address + " found in bonded devices"); 1541 } 1542 } 1543 } 1544 mProfileToConnectableDevicesMap.put(profile, devicesInfo); 1545 // Check to see if there are any primary or secondary devices for this profile and 1546 // update BluetoothDevicesInfo with the priority information. 1547 for (int priority : mPrioritiesSupported) { 1548 readAndTagDeviceWithPriorityFromSettings(profile, priority); 1549 } 1550 } 1551 return true; 1552 } 1553 1554 /** 1555 * Read from Secure Settings if there are primary or secondary devices marked for this 1556 * Bluetooth profile. If there are tagged devices, update the BluetoothDevicesInfo so the 1557 * policy can prioritize those devices when making connection attempts. 1558 * 1559 * @param profile - Bluetooth Profile to check 1560 * @param priority - Priority to check 1561 */ 1562 private void readAndTagDeviceWithPriorityFromSettings(int profile, int priority) { 1563 BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile); 1564 if (devicesInfo == null) { 1565 return; 1566 } 1567 if (!mCarBluetoothService.isPriorityDevicePresent(profile, priority)) { 1568 // There is no device for this priority - either it hasn't been set or has been removed. 1569 // So check if the policy has a device associated with this priority and remove it. 1570 BluetoothDevice deviceToClear = devicesInfo.getBluetoothDeviceForPriorityLocked( 1571 priority); 1572 if (deviceToClear != null) { 1573 if (DBG) { 1574 Log.d(TAG, "Clearing priority for: " + 1575 Utils.getDeviceDebugInfo(deviceToClear)); 1576 } 1577 devicesInfo.removeBluetoothDevicePriorityLocked(deviceToClear); 1578 } 1579 } else { 1580 // There is a device with the given priority for the given profile. Update the 1581 // policy's records. 1582 String deviceName = mCarBluetoothService.getDeviceNameWithPriority(profile, 1583 priority); 1584 if (deviceName != null) { 1585 BluetoothDevice bluetoothDevice = getBondedDeviceWithGivenName(deviceName); 1586 if (bluetoothDevice != null) { 1587 if (DBG) { 1588 Log.d(TAG, "Setting priority: " + priority + " for " + deviceName); 1589 } 1590 tagDeviceWithPriority(bluetoothDevice, profile, priority); 1591 } 1592 } 1593 } 1594 } 1595 1596 /** 1597 * Tag a Bluetooth device with priority - Primary or Secondary. This only updates the policy's 1598 * record (BluetoothDevicesInfo) of the priority information. 1599 * 1600 * @param device - BluetoothDevice to tag 1601 * @param profile - BluetoothProfile to tag 1602 * @param priority - Priority to tag with 1603 */ 1604 @VisibleForTesting 1605 void tagDeviceWithPriority(BluetoothDevice device, int profile, int priority) { 1606 BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile); 1607 if (device != null) { 1608 if (DBG) { 1609 Log.d(TAG, "Profile: " + profile + " : " + device + " Priority: " + priority); 1610 } 1611 devicesInfo.setBluetoothDevicePriorityLocked(device, priority); 1612 } 1613 } 1614 1615 /** 1616 * Given the device name, find the corresponding {@link BluetoothDevice} from the list of 1617 * Bonded devices. 1618 * 1619 * @param name Bluetooth Device name 1620 */ 1621 @Nullable 1622 private BluetoothDevice getBondedDeviceWithGivenName(String name) { 1623 if (mBluetoothAdapter == null) { 1624 if (DBG) { 1625 Log.d(TAG, "Bluetooth Adapter Null"); 1626 } 1627 return null; 1628 } 1629 if (name == null) { 1630 Log.w(TAG, "getBondedDeviceWithGivenName() Passing in a null name"); 1631 return null; 1632 } 1633 if (DBG) { 1634 Log.d(TAG, "Looking for bonded device: " + name); 1635 } 1636 BluetoothDevice btDevice = null; 1637 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); 1638 for (BluetoothDevice bd : bondedDevices) { 1639 if (name.equals(bd.getAddress())) { 1640 btDevice = bd; 1641 break; 1642 } 1643 } 1644 return btDevice; 1645 } 1646 1647 1648 public void dump(PrintWriter writer) { 1649 writer.println("*BluetoothDeviceConnectionPolicy*"); 1650 printDeviceMap(writer); 1651 mBluetoothAutoConnectStateMachine.dump(writer); 1652 } 1653 } 1654