Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 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.bluetooth.BluetoothDevice; |
| 20 | import android.util.Log; |
| 21 | |
| 22 | import java.util.List; |
| 23 | import java.util.ArrayList; |
| 24 | import java.util.Collections; |
| 25 | |
| 26 | /** |
| 27 | * BluetoothDevicesInfo contains all the information pertinent to connection on a Bluetooth Profile. |
| 28 | * It holds |
| 29 | * 1. a list of devices {@link #mDeviceList} that has previously paired and connected on this |
| 30 | * profile. |
| 31 | * 2. a Connection Info object {@link #mConnectionInfo} that has following book keeping information: |
| 32 | * a) profile |
| 33 | * b) Current Connection status |
| 34 | * c) If there are any devices available for connection |
| 35 | * d) Index of the Device list that a connection is being tried upon currently. |
| 36 | * e) Number of devices that have been previously paired and connected on this profile. |
| 37 | * f) How many retry attempts have been made |
| 38 | * |
| 39 | * This is used by the {@link BluetoothDeviceConnectionPolicy} to find the device to attempt |
| 40 | * a connection on for a profile. The policy also updates this object with the connection |
| 41 | * results. |
| 42 | */ |
| 43 | public class BluetoothDevicesInfo { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 44 | private static final String TAG = "CarBluetoothDevicesInfo"; |
| 45 | private static final boolean DBG = false; |
| 46 | private final int DEVICE_NOT_FOUND = -1; |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 47 | |
| 48 | private class ConnectionInfo { |
| 49 | int mProfile; |
| 50 | boolean mIsConnected; |
| 51 | boolean mDeviceAvailableToConnect; |
| 52 | int mDeviceIndex; |
| 53 | int mNumPairedDevices; |
| 54 | int mRetryAttempt; |
| 55 | |
| 56 | public ConnectionInfo(int profile) { |
| 57 | mProfile = profile; |
| 58 | mIsConnected = false; |
| 59 | mDeviceAvailableToConnect = true; |
| 60 | mDeviceIndex = 0; |
| 61 | mNumPairedDevices = 0; |
| 62 | mRetryAttempt = 0; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | private List<BluetoothDevice> mDeviceList; |
| 67 | private ConnectionInfo mConnectionInfo; |
| 68 | |
| 69 | public BluetoothDevicesInfo(int profile) { |
| 70 | mDeviceList = new ArrayList<BluetoothDevice>(); |
| 71 | mConnectionInfo = new ConnectionInfo(profile); |
| 72 | } |
| 73 | |
| 74 | /** |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 75 | * Get the position of the given device in the list of connectable deviecs for this profile. |
| 76 | * |
| 77 | * @param device - {@link BluetoothDevice} |
| 78 | * @return postion in the {@link #mDeviceList}, DEVICE_NOT_FOUND if the device is not in the |
| 79 | * list. |
| 80 | */ |
| 81 | private int getPositionInList(BluetoothDevice device) { |
| 82 | int index = DEVICE_NOT_FOUND; |
| 83 | if (mDeviceList != null) { |
| 84 | int i = 0; |
| 85 | for (BluetoothDevice dev : mDeviceList) { |
| 86 | if (dev.getName().equals(device.getName())) { |
| 87 | index = i; |
| 88 | break; |
| 89 | } |
| 90 | i++; |
| 91 | } |
| 92 | } |
| 93 | return index; |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Check if the given device is in the {@link #mDeviceList} |
| 98 | * |
| 99 | * @param device - {@link BluetoothDevice} to look for |
| 100 | * @return true if found, false if not found |
| 101 | */ |
| 102 | private boolean checkDeviceInList(BluetoothDevice device) { |
| 103 | boolean isPresent = false; |
| 104 | if (device == null) { |
| 105 | return isPresent; |
| 106 | } |
| 107 | for (BluetoothDevice dev : mDeviceList) { |
| 108 | if (dev.getAddress().equals(device.getAddress())) { |
| 109 | isPresent = true; |
| 110 | break; |
| 111 | } |
| 112 | } |
| 113 | return isPresent; |
| 114 | } |
| 115 | |
| 116 | /** |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 117 | * Get the current list of connectable devices for this profile. |
| 118 | * |
| 119 | * @return Device list for this profile. |
| 120 | */ |
| 121 | public List<BluetoothDevice> getDeviceList() { |
| 122 | return mDeviceList; |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Add a device to the device list. Used during pairing. |
| 127 | * |
| 128 | * @param dev - device to add for further connection attempts on this profile. |
| 129 | */ |
| 130 | public void addDeviceLocked(BluetoothDevice dev) { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 131 | if (checkDeviceInList(dev)) { |
| 132 | if (DBG) { |
| 133 | Log.d(TAG, "Device " + dev.getName() + "already in list. Not adding"); |
| 134 | } |
| 135 | return; |
| 136 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 137 | if (mDeviceList != null) { |
| 138 | mDeviceList.add(dev); |
| 139 | } else { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 140 | if (DBG) { |
| 141 | Log.d(TAG, "Device List is null"); |
| 142 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 143 | } |
| 144 | if (mConnectionInfo != null) { |
| 145 | mConnectionInfo.mNumPairedDevices++; |
| 146 | } else { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 147 | if (DBG) { |
| 148 | Log.d(TAG, "ConnectionInfo is null"); |
| 149 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 150 | } |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Remove a device from the list. Used when a device is unpaired |
| 155 | * |
| 156 | * @param dev - device to remove from the list. |
| 157 | */ |
| 158 | public void removeDeviceLocked(BluetoothDevice dev) { |
| 159 | if (mDeviceList != null) { |
| 160 | mDeviceList.remove(dev); |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 161 | } else { |
| 162 | if (DBG) { |
| 163 | Log.d(TAG, "Device List is null"); |
| 164 | } |
| 165 | } |
| 166 | if (mConnectionInfo != null) { |
| 167 | mConnectionInfo.mNumPairedDevices--; |
| 168 | } else { |
| 169 | if (DBG) { |
| 170 | Log.d(TAG, "ConnectionInfo is null"); |
| 171 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 172 | } |
| 173 | } |
| 174 | |
| 175 | |
| 176 | /** |
| 177 | * Returns the next device to attempt a connection on for this profile. |
| 178 | * |
| 179 | * @return {@link BluetoothDevice} that is next in the Queue. |
| 180 | * null if the Queue has been exhausted (no known device nearby) |
| 181 | */ |
| 182 | public BluetoothDevice getNextDeviceInQueueLocked() { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 183 | if (DBG) { |
| 184 | Log.d(TAG, "Getting device " + mConnectionInfo.mDeviceIndex + " from list"); |
| 185 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 186 | if (mConnectionInfo.mDeviceIndex >= mConnectionInfo.mNumPairedDevices) { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 187 | if (DBG) { |
| 188 | Log.d(TAG, |
| 189 | "No device available for profile " |
| 190 | + mConnectionInfo.mProfile + " " |
| 191 | + mConnectionInfo.mNumPairedDevices); |
| 192 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 193 | mConnectionInfo.mDeviceIndex = 0; //reset the index |
| 194 | return null; |
| 195 | } |
| 196 | return mDeviceList.get(mConnectionInfo.mDeviceIndex); |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Update the connection Status for connection attempts made on this profile. |
| 201 | * If the attempt was successful, mark it and keep track of the device that was connected. |
| 202 | * If unsuccessful, check if we can retry on the same device. If no more retry attempts, |
| 203 | * move to the next device in the Queue. |
| 204 | * |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 205 | * @param device - {@link BluetoothDevice} that connected. |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 206 | * @param success - connection result |
| 207 | * @param retry - If Retries are available for the same device. |
| 208 | */ |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 209 | public void updateConnectionStatusLocked(BluetoothDevice device, boolean success, |
| 210 | boolean retry) { |
| 211 | if (device == null) { |
| 212 | Log.w(TAG, "Updating Status with null BluetoothDevice"); |
| 213 | return; |
| 214 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 215 | mConnectionInfo.mIsConnected = success; |
| 216 | if (success) { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 217 | if (DBG) { |
| 218 | Log.d(TAG, mConnectionInfo.mProfile + " connected to " + device.getName()); |
| 219 | } |
| 220 | // b/34722344 - TODO |
| 221 | // Get the position of this device in the device list maintained for this profile. |
| 222 | int positionInQ = getPositionInList(device); |
| 223 | if (DBG) { |
| 224 | Log.d(TAG, "Position of " + device.getName() + " in Q: " + positionInQ); |
| 225 | } |
| 226 | // If the device that connected is not in the list, it could be because it is being |
| 227 | // paired and getting added to the device list for this profile for the first time. |
| 228 | if (positionInQ == DEVICE_NOT_FOUND) { |
| 229 | Log.d(TAG, "Connected device not in Q: " + device.getName()); |
| 230 | addDeviceLocked(device); |
| 231 | positionInQ = mDeviceList.size() - 1; |
| 232 | } else if (positionInQ != mConnectionInfo.mDeviceIndex) { |
| 233 | // This will happen if auto-connect request a connect on a device from its list, |
| 234 | // but the device that connected was different. Maybe there was another requestor |
| 235 | // and the Bluetooth services chose to honor the other request. What we do here, |
| 236 | // is to make sure we note which device connected and not assume that the device |
| 237 | // that connected is the device we requested. The ultimate goal of the policy is |
| 238 | // to remember which devices connected on which profile (regardless of the origin |
| 239 | // of the connection request) so it knows which device to connect the next time. |
| 240 | if (DBG) { |
| 241 | Log.d(TAG, "Different device connected: " + device.getName()); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | // At this point positionInQ reflects where in the list the device that connected is, |
| 246 | // i.e, its index. Move the device to the front of the device list, since the policy is |
| 247 | // to try to connect to the last connected device first. Hence by moving the device |
| 248 | // to the front of the list, the next time auto connect triggers, this will be the |
| 249 | // device that the policy will try to connect on for this profile. |
| 250 | if (positionInQ != 0) { |
| 251 | moveToFrontLocked(mDeviceList, positionInQ); |
| 252 | // reset the device Index back to the first in the Queue |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 253 | mConnectionInfo.mDeviceIndex = 0; |
| 254 | } |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 255 | // Reset the retry count |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 256 | mConnectionInfo.mRetryAttempt = 0; |
| 257 | } else { |
| 258 | // if no more retries, move to the next device |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 259 | if (DBG) { |
| 260 | Log.d(TAG, "Connection fail or Disconnected"); |
| 261 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 262 | if (!retry) { |
| 263 | mConnectionInfo.mDeviceIndex++; |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 264 | if (DBG) { |
| 265 | Log.d(TAG, "Moving to device: " + mConnectionInfo.mDeviceIndex); |
| 266 | } |
| 267 | // Reset the retry count |
| 268 | mConnectionInfo.mRetryAttempt = 0; |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 269 | } else { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 270 | if (DBG) { |
| 271 | Log.d(TAG, "Staying with the same device - retrying: " |
| 272 | + mConnectionInfo.mDeviceIndex); |
| 273 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | /** |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 279 | * Move the item in the given position to the front of the list and push the rest down. |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 280 | * |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 281 | * @param deviceList - list of bluetooth devices to operate on |
| 282 | * @param position - position of the device to move from |
| 283 | */ |
| 284 | private void moveToFrontLocked(List<BluetoothDevice> deviceList, int position) { |
| 285 | BluetoothDevice deviceToMove = deviceList.get(position); |
| 286 | if (deviceToMove == null) { |
| 287 | if (DBG) { |
| 288 | Log.d(TAG, "Unexpected: deviceToMove is null"); |
| 289 | } |
| 290 | return; |
| 291 | } |
| 292 | deviceList.remove(position); |
| 293 | deviceList.add(0, deviceToMove); |
| 294 | |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * Returns the profile that this devicesInfo is for. |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 299 | */ |
| 300 | public Integer getProfileLocked() { |
| 301 | return mConnectionInfo.mProfile; |
| 302 | } |
| 303 | |
| 304 | /** |
| 305 | * Return if the profile is currently connected. |
| 306 | * |
| 307 | * @return true if connected, false if not. |
| 308 | */ |
| 309 | public boolean isConnectedLocked() { |
| 310 | return mConnectionInfo.mIsConnected; |
| 311 | } |
| 312 | |
| 313 | /** |
| 314 | * Get the number of devices in the {@link #mDeviceList} - paired and previously connected |
| 315 | * devices |
| 316 | * |
| 317 | * @return number of paired devices on this profile. |
| 318 | */ |
| 319 | public Integer getNumberOfPairedDevicesLocked() { |
| 320 | return mConnectionInfo.mNumPairedDevices; |
| 321 | } |
| 322 | |
| 323 | /** |
| 324 | * Increment the retry count. Called when a connection is made on the profile. |
| 325 | */ |
| 326 | public void incrementRetryCountLocked() { |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 327 | if (mConnectionInfo != null) { |
| 328 | mConnectionInfo.mRetryAttempt++; |
| 329 | } |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 330 | } |
| 331 | |
| 332 | /** |
| 333 | * Get the number of times a connection attempt has been tried on a device for this profile. |
| 334 | * |
| 335 | * @return number of retry attempts. |
| 336 | */ |
| 337 | public Integer getRetryCountLocked() { |
| 338 | return mConnectionInfo.mRetryAttempt; |
| 339 | } |
| 340 | |
| 341 | /** |
| 342 | * Check if we have iterated through all the devices of the {@link #mDeviceList} |
| 343 | * |
| 344 | * @return true if there are still devices to try connecting |
| 345 | * false if we have unsuccessfully tried connecting to all the devices in the {@link |
| 346 | * #mDeviceList} |
| 347 | */ |
| 348 | public boolean isDeviceAvailableToConnectLocked() { |
| 349 | return mConnectionInfo.mDeviceAvailableToConnect; |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * Set the mDeviceAvailableToConnect with the passed value. |
| 354 | * |
| 355 | * @param deviceAvailable - true or false. |
| 356 | */ |
| 357 | public void setDeviceAvailableToConnectLocked(boolean deviceAvailable) { |
| 358 | mConnectionInfo.mDeviceAvailableToConnect = deviceAvailable; |
| 359 | } |
| 360 | |
| 361 | /** |
| 362 | * Reset the connection related bookkeeping information. |
| 363 | * Called on a BluetoothAdapter Off to clean slate |
| 364 | */ |
| 365 | public void resetConnectionInfoLocked() { |
| 366 | mConnectionInfo.mIsConnected = false; |
| 367 | mConnectionInfo.mDeviceIndex = 0; |
| 368 | mConnectionInfo.mRetryAttempt = 0; |
| 369 | mConnectionInfo.mDeviceAvailableToConnect = true; |
| 370 | } |
| 371 | |
Ram Periathiruvadi | ee28c00 | 2017-02-07 21:35:01 -0800 | [diff] [blame] | 372 | public void resetDeviceListLocked() { |
| 373 | if (mDeviceList != null) { |
| 374 | mDeviceList.clear(); |
| 375 | mConnectionInfo.mNumPairedDevices = 0; |
| 376 | } |
| 377 | resetConnectionInfoLocked(); |
| 378 | } |
| 379 | |
Ram Periathiruvadi | 7ed8418 | 2017-01-20 15:18:08 -0800 | [diff] [blame] | 380 | } |