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 android.annotation.Nullable; 20 import android.bluetooth.BluetoothDevice; 21 import android.util.Log; 22 23 import java.util.List; 24 import java.util.ArrayList; 25 26 import android.bluetooth.BluetoothProfile; 27 28 /** 29 * BluetoothDevicesInfo contains all the information pertinent to connection on a Bluetooth Profile. 30 * It holds 31 * 1. a list of devices {@link #mDeviceInfoList} that has previously paired and connected on this 32 * profile. 33 * 2. a Connection Info object {@link #mConnectionInfo} that has following book keeping information: 34 * a) profile 35 * b) Current Connection status 36 * c) If there are any devices available for connection 37 * d) Index of the Device list that a connection is being tried upon currently. 38 * e) Number of devices that have been previously paired and connected on this profile. 39 * f) How many retry attempts have been made 40 * 41 * This is used by the {@link BluetoothDeviceConnectionPolicy} to find the device to attempt 42 * a connection on for a profile. The policy also updates this object with the connection 43 * results. 44 */ 45 public class BluetoothDevicesInfo { 46 47 private static final String TAG = "CarBluetoothDevicesInfo"; 48 private static final boolean DBG = false; 49 private final int DEVICE_NOT_FOUND = -1; 50 // The device list and the connection state information together have all the information 51 // that is required to know which device(s) to connect to, when we need to connect/ 52 private List<DeviceInfo> mDeviceInfoList; 53 private ConnectionInfo mConnectionInfo; 54 55 /** 56 * This class holds on to information regarding this bluetooth profile's connection state. 57 */ 58 private class ConnectionInfo { 59 // which bluetooth profile this Device Info is for 60 private int mProfile; 61 // are there any devices available to connect. It is false either if 62 // 1. no device has been paired to connect on this profile 63 // 2. all paired devices have been tried to connect to, but unsuccessful (not in range etc) 64 private boolean mDeviceAvailableToConnect; 65 // index of device in the mDeviceInfoList that the next connection attempt should be made 66 private int mDeviceIndex; 67 // Connection Retry counter 68 private int mRetryAttempt; 69 // Current number of active connections on this profile 70 private int mNumActiveConnections; 71 // number of concurrent active connections supported. 72 private int mNumConnectionsSupported; 73 ConnectionInfo(int profile)74 public ConnectionInfo(int profile) { 75 // Default the number of concurrent active connections supported to 1. 76 this(profile, 1); 77 } 78 ConnectionInfo(int profile, int numConnectionsSupported)79 public ConnectionInfo(int profile, int numConnectionsSupported) { 80 mProfile = profile; 81 mNumConnectionsSupported = numConnectionsSupported; 82 initConnectionInfo(); 83 } 84 initConnectionInfo()85 private void initConnectionInfo() { 86 mDeviceAvailableToConnect = true; 87 mDeviceIndex = 0; 88 mRetryAttempt = 0; 89 mNumActiveConnections = 0; 90 } 91 } 92 93 /** 94 * This class holds information about the list of devices that can connect (have connected in 95 * the past) and their current connection state. 96 */ 97 public class DeviceInfo { 98 99 private BluetoothDevice mBluetoothDevice; 100 private int mConnectionState; 101 DeviceInfo(BluetoothDevice device, int state)102 public DeviceInfo(BluetoothDevice device, int state) { 103 mBluetoothDevice = device; 104 mConnectionState = state; 105 } 106 setConnectionState(int state)107 public void setConnectionState(int state) { 108 mConnectionState = state; 109 } 110 getConnectionState()111 public int getConnectionState() { 112 return mConnectionState; 113 } 114 getBluetoothDevice()115 public BluetoothDevice getBluetoothDevice() { 116 return mBluetoothDevice; 117 } 118 } 119 BluetoothDevicesInfo(int profile)120 public BluetoothDevicesInfo(int profile) { 121 mDeviceInfoList = new ArrayList<>(); 122 mConnectionInfo = new ConnectionInfo(profile); 123 } 124 BluetoothDevicesInfo(int profile, int numConnectionsSupported)125 public BluetoothDevicesInfo(int profile, int numConnectionsSupported) { 126 mDeviceInfoList = new ArrayList<>(); 127 mConnectionInfo = new ConnectionInfo(profile, numConnectionsSupported); 128 } 129 130 /** 131 * Get the position of the given device in the list of connectable devices for this profile. 132 * 133 * @param device - {@link BluetoothDevice} 134 * @return postion in the {@link #mDeviceInfoList}, DEVICE_NOT_FOUND if the device is not in the 135 * list. 136 */ getPositionInListLocked(BluetoothDevice device)137 private int getPositionInListLocked(BluetoothDevice device) { 138 int index = DEVICE_NOT_FOUND; 139 if (mDeviceInfoList != null) { 140 int i = 0; 141 for (DeviceInfo devInfo : mDeviceInfoList) { 142 if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) { 143 index = i; 144 break; 145 } 146 i++; 147 } 148 } 149 return index; 150 } 151 152 /** 153 * Check if the given device is in the {@link #mDeviceInfoList} 154 * 155 * @param device - {@link BluetoothDevice} to look for 156 * @return true if found, false if not found 157 */ checkDeviceInListLocked(BluetoothDevice device)158 private boolean checkDeviceInListLocked(BluetoothDevice device) { 159 boolean isPresent = false; 160 if (device == null) { 161 return isPresent; 162 } 163 for (DeviceInfo devInfo : mDeviceInfoList) { 164 if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) { 165 isPresent = true; 166 break; 167 } 168 } 169 return isPresent; 170 } 171 findDeviceInfoInListLocked(@ullable BluetoothDevice device)172 private DeviceInfo findDeviceInfoInListLocked(@Nullable BluetoothDevice device) { 173 if (device == null) { 174 return null; 175 } 176 for (DeviceInfo devInfo : mDeviceInfoList) { 177 if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) { 178 return devInfo; 179 } 180 } 181 return null; 182 } 183 /** 184 * Get the current list of connectable devices for this profile. 185 * 186 * @return Device list for this profile. 187 */ getDeviceList()188 public List<BluetoothDevice> getDeviceList() { 189 List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>(); 190 for (DeviceInfo deviceInfo : mDeviceInfoList) { 191 bluetoothDeviceList.add(deviceInfo.mBluetoothDevice); 192 } 193 return bluetoothDeviceList; 194 } 195 getDeviceInfoList()196 public List<DeviceInfo> getDeviceInfoList() { 197 return mDeviceInfoList; 198 } 199 setNumberOfConnectionsSupported(int num)200 public void setNumberOfConnectionsSupported(int num) { 201 mConnectionInfo.mNumConnectionsSupported = num; 202 } 203 getNumberOfConnectionsSupported()204 public int getNumberOfConnectionsSupported() { 205 return mConnectionInfo.mNumConnectionsSupported; 206 } 207 208 /** 209 * Add a device to the device list. Used during pairing. 210 * 211 * @param dev - device to add for further connection attempts on this profile. 212 */ addDeviceLocked(BluetoothDevice dev)213 public void addDeviceLocked(BluetoothDevice dev) { 214 // Check if this device is already in the device list 215 if (checkDeviceInListLocked(dev)) { 216 if (DBG) { 217 Log.d(TAG, "Device " + dev + " already in list. Not adding"); 218 } 219 return; 220 } 221 // Add new device and set the connection state to DISCONNECTED. 222 if (mDeviceInfoList != null) { 223 DeviceInfo deviceInfo = new DeviceInfo(dev, BluetoothProfile.STATE_DISCONNECTED); 224 mDeviceInfoList.add(deviceInfo); 225 } else { 226 if (DBG) { 227 Log.d(TAG, "Device List is null"); 228 } 229 } 230 } 231 232 /** 233 * Set the connection state for this device to the given connection state. 234 * 235 * @param device - Bluetooth device to update the state for 236 * @param state - the Connection state to set. 237 */ setConnectionStateLocked(BluetoothDevice device, int state)238 public void setConnectionStateLocked(BluetoothDevice device, int state) { 239 if (device == null) { 240 Log.e(TAG, "setConnectionStateLocked() device null"); 241 return; 242 } 243 for (DeviceInfo devInfo : mDeviceInfoList) { 244 BluetoothDevice dev = devInfo.mBluetoothDevice; 245 if (dev == null) { 246 continue; 247 } 248 if (dev.getAddress().equals(device.getAddress())) { 249 if (DBG) { 250 Log.d(TAG, "Setting " + dev + " state to " + state); 251 } 252 devInfo.setConnectionState(state); 253 break; 254 } 255 } 256 } 257 258 /** 259 * Returns the current connection state for the given device 260 * 261 * @param device - device to get the bluetooth connection state for 262 * @return - Connection State. If passed device is null, returns DEVICE_NOT_FOUND. 263 */ getConnectionStateLocked(BluetoothDevice device)264 public int getConnectionStateLocked(BluetoothDevice device) { 265 int state = DEVICE_NOT_FOUND; 266 if (device == null) { 267 Log.e(TAG, "getConnectionStateLocked() device null"); 268 return state; 269 } 270 271 for (DeviceInfo devInfo : mDeviceInfoList) { 272 BluetoothDevice dev = devInfo.mBluetoothDevice; 273 if (dev == null) { 274 continue; 275 } 276 if (dev.getAddress().equals(device.getAddress())) { 277 state = devInfo.getConnectionState(); 278 break; 279 } 280 } 281 return state; 282 } 283 284 /** 285 * Remove a device from the list. Used when a device is unpaired 286 * 287 * @param dev - device to remove from the list. 288 */ removeDeviceLocked(BluetoothDevice dev)289 public void removeDeviceLocked(BluetoothDevice dev) { 290 if (mDeviceInfoList != null) { 291 DeviceInfo devInfo = findDeviceInfoInListLocked(dev); 292 if (devInfo != null) { 293 mDeviceInfoList.remove(devInfo); 294 // If the device was connected when it was unpaired, we wouldn't have received the 295 // Profile disconnected intents. Hence check if the device was connected and if it 296 // was, then decrement the number of active connections. 297 if (devInfo.getConnectionState() == BluetoothProfile.STATE_CONNECTED) { 298 mConnectionInfo.mNumActiveConnections--; 299 } 300 } 301 } else { 302 if (DBG) { 303 Log.d(TAG, "Device List is null"); 304 } 305 } 306 Log.d(TAG, "Device List size: " + mDeviceInfoList.size()); 307 } 308 clearDeviceListLocked()309 public void clearDeviceListLocked() { 310 if (mDeviceInfoList != null) { 311 mDeviceInfoList.clear(); 312 } 313 } 314 315 /** 316 * Returns the next device to attempt a connection on for this profile. 317 * 318 * @return {@link BluetoothDevice} that is next in the Queue. null if the Queue has been 319 * exhausted 320 * (no known device nearby) 321 */ getNextDeviceInQueueLocked()322 public BluetoothDevice getNextDeviceInQueueLocked() { 323 BluetoothDevice device = null; 324 int numberOfPairedDevices = getNumberOfPairedDevicesLocked(); 325 if (mConnectionInfo.mDeviceIndex >= numberOfPairedDevices) { 326 if (DBG) { 327 Log.d(TAG, 328 "No device available for profile " 329 + mConnectionInfo.mProfile + " " 330 + mConnectionInfo.mDeviceIndex + "/" 331 + numberOfPairedDevices); 332 } 333 // mDeviceIndex is the index of the device in the mDeviceInfoList, that the next 334 // connection attempt would be made on. It is moved ahead on 335 // updateConnectionStatusLocked() so it always holds the index of the next device to 336 // connect to. But here, when we get the next device to connect to, if we see that 337 // the index is greater than the number of devices in the list, then we move the index 338 // back to the first device in the list and don't return anything. 339 // The reason why this is reset is to imply that connection attempts on this profile has 340 // been exhausted and if you want to retry connecting on this profile, we will start 341 // from the first device. 342 // The reason to reset here rather than in updateConnectionStatusLocked() is to make 343 // sure we have the latest view of the numberOfPairedDevices before we say we have 344 // exhausted the list. 345 mConnectionInfo.mDeviceIndex = 0; 346 return null; 347 } 348 device = mDeviceInfoList.get(mConnectionInfo.mDeviceIndex).mBluetoothDevice; 349 if (DBG) { 350 Log.d(TAG, "Getting device " + mConnectionInfo.mDeviceIndex + " from list: " 351 + device); 352 } 353 return device; 354 } 355 356 /** 357 * Update the connection Status for connection attempts made on this profile. 358 * If the attempt was successful, mark it and keep track of the device that was connected. 359 * If unsuccessful, check if we can retry on the same device. If no more retry attempts, 360 * move to the next device in the Queue. 361 * 362 * @param device - {@link BluetoothDevice} that connected. 363 * @param success - connection result 364 * @param retry - If Retries are available for the same device. 365 */ updateConnectionStatusLocked(BluetoothDevice device, boolean success, boolean retry)366 public void updateConnectionStatusLocked(BluetoothDevice device, boolean success, 367 boolean retry) { 368 if (device == null) { 369 Log.w(TAG, "Updating Status with null BluetoothDevice"); 370 return; 371 } 372 if (success) { 373 if (DBG) { 374 Log.d(TAG, mConnectionInfo.mProfile + " connected to " + device); 375 } 376 // b/34722344 - TODO 377 // Get the position of this device in the device list maintained for this profile. 378 int positionInQ = getPositionInListLocked(device); 379 if (DBG) { 380 Log.d(TAG, "Position of " + device + " in Q: " + positionInQ); 381 } 382 // If the device that connected is not in the list, it could be because it is being 383 // paired and getting added to the device list for this profile for the first time. 384 if (positionInQ == DEVICE_NOT_FOUND) { 385 Log.d(TAG, "Connected device not in Q: " + device); 386 addDeviceLocked(device); 387 positionInQ = mDeviceInfoList.size() - 1; 388 } else if (positionInQ != mConnectionInfo.mDeviceIndex) { 389 // This will happen if auto-connect request a connect on a device from its list, 390 // but the device that connected was different. Maybe there was another requestor 391 // and the Bluetooth services chose to honor the other request. What we do here, 392 // is to make sure we note which device connected and not assume that the device 393 // that connected is the device we requested. The ultimate goal of the policy is 394 // to remember which devices connected on which profile (regardless of the origin 395 // of the connection request) so it knows which device to connect the next time. 396 if (DBG) { 397 Log.d(TAG, "Different device connected: " + device + " CurrIndex: " 398 + mConnectionInfo.mDeviceIndex); 399 } 400 } 401 402 // At this point positionInQ reflects where in the list the device that connected is, 403 // i.e, its index. Move the device to the front of the device list, since the policy is 404 // to try to connect to the last connected device first. Hence by moving the device 405 // to the front of the list, the next time auto connect triggers, this will be the 406 // device that the policy will try to connect on for this profile. 407 if (positionInQ != 0) { 408 moveToFrontLocked(positionInQ); 409 // reset the device Index back to the first in the Queue 410 //mConnectionInfo.mDeviceIndex = 0; 411 } 412 setConnectionStateLocked(device, BluetoothProfile.STATE_CONNECTED); 413 // Reset the retry count 414 mConnectionInfo.mRetryAttempt = 0; 415 mConnectionInfo.mNumActiveConnections++; 416 mConnectionInfo.mDeviceIndex++; 417 if (DBG) { 418 Log.d(TAG, 419 "Profile: " + mConnectionInfo.mProfile + " Number of Active Connections: " 420 + mConnectionInfo.mNumActiveConnections); 421 } 422 } else { 423 // if no more retries, move to the next device 424 if (DBG) { 425 Log.d(TAG, "Connection fail or Disconnected"); 426 } 427 // only if the given device was already connected decrement this. 428 if (getConnectionStateLocked(device) == BluetoothProfile.STATE_CONNECTED) { 429 mConnectionInfo.mNumActiveConnections--; 430 } 431 setConnectionStateLocked(device, BluetoothProfile.STATE_DISCONNECTED); 432 if (!retry) { 433 mConnectionInfo.mDeviceIndex++; 434 if (DBG) { 435 Log.d(TAG, "Moving to device: " + mConnectionInfo.mDeviceIndex); 436 } 437 // Reset the retry count 438 mConnectionInfo.mRetryAttempt = 0; 439 } else { 440 if (DBG) { 441 Log.d(TAG, "Staying with the same device - retrying: " 442 + mConnectionInfo.mDeviceIndex); 443 } 444 } 445 } 446 } 447 448 /** 449 * Move the item in the given position to the front of the list and push the rest down. 450 * 451 * @param position - position of the device to move from 452 */ moveToFrontLocked(int position)453 private void moveToFrontLocked(int position) { 454 DeviceInfo deviceInfo = mDeviceInfoList.get(position); 455 if (deviceInfo.mBluetoothDevice == null) { 456 if (DBG) { 457 Log.d(TAG, "Unexpected: deviceToMove is null"); 458 } 459 return; 460 } 461 mDeviceInfoList.remove(position); 462 mDeviceInfoList.add(0, deviceInfo); 463 } 464 465 /** 466 * Returns the profile that this devicesInfo is for. 467 */ getProfileLocked()468 public Integer getProfileLocked() { 469 return mConnectionInfo.mProfile; 470 } 471 472 /** 473 * Get the number of devices in the {@link #mDeviceInfoList} - paired and previously connected 474 * devices 475 * 476 * @return number of paired devices on this profile. 477 */ getNumberOfPairedDevicesLocked()478 public int getNumberOfPairedDevicesLocked() { 479 return mDeviceInfoList.size(); 480 } 481 482 /** 483 * Increment the retry count. Called when a connection is made on the profile. 484 */ incrementRetryCountLocked()485 public void incrementRetryCountLocked() { 486 if (mConnectionInfo != null) { 487 mConnectionInfo.mRetryAttempt++; 488 } 489 } 490 491 /** 492 * Get the number of times a connection attempt has been tried on a device for this profile. 493 * 494 * @return number of retry attempts. 495 */ getRetryCountLocked()496 public Integer getRetryCountLocked() { 497 return mConnectionInfo.mRetryAttempt; 498 } 499 500 /** 501 * Set the mDeviceAvailableToConnect with the passed value. 502 * 503 * @param deviceAvailable - true or false. 504 */ setDeviceAvailableToConnectLocked(boolean deviceAvailable)505 public void setDeviceAvailableToConnectLocked(boolean deviceAvailable) { 506 mConnectionInfo.mDeviceAvailableToConnect = deviceAvailable; 507 } 508 509 /** 510 * Returns if there are any devices available to connect on this profile. 511 * 512 * @return true if a device is available, false 513 * 1. if number of active connections on this profile has been maxed out or 514 * 2. if all devices in the list have failed to connect already. 515 */ isProfileConnectableLocked()516 public boolean isProfileConnectableLocked() { 517 if (DBG) { 518 Log.d(TAG, "Profile: " + mConnectionInfo.mProfile + " Num of connections: " 519 + mConnectionInfo.mNumActiveConnections + " Conn Supported: " 520 + mConnectionInfo.mNumConnectionsSupported); 521 } 522 523 if (mConnectionInfo.mDeviceAvailableToConnect && 524 mConnectionInfo.mNumActiveConnections < mConnectionInfo.mNumConnectionsSupported) { 525 return true; 526 } 527 return false; 528 529 } 530 531 /** 532 * Return the current number of active connections on this profile. 533 * 534 * @return number of active connections. 535 */ getNumberOfActiveConnectionsLocked()536 public int getNumberOfActiveConnectionsLocked() { 537 return mConnectionInfo.mNumActiveConnections; 538 } 539 540 /** 541 * Reset the connection related bookkeeping information. 542 * Called on a BluetoothAdapter Off to clean slate 543 */ resetConnectionInfoLocked()544 public void resetConnectionInfoLocked() { 545 mConnectionInfo.mNumActiveConnections = 0; 546 mConnectionInfo.mDeviceIndex = 0; 547 mConnectionInfo.mRetryAttempt = 0; 548 mConnectionInfo.mDeviceAvailableToConnect = true; 549 for (DeviceInfo info : mDeviceInfoList) { 550 setConnectionStateLocked(info.getBluetoothDevice(), 551 BluetoothProfile.STATE_DISCONNECTED); 552 } 553 } 554 resetDeviceListLocked()555 public void resetDeviceListLocked() { 556 if (mDeviceInfoList != null) { 557 mDeviceInfoList.clear(); 558 } 559 resetConnectionInfoLocked(); 560 } 561 562 } 563