blob: dfd3d0d6854450be000c635f621a56c5ec67fedf [file] [log] [blame]
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001/*
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
17package com.android.car;
18
19import android.bluetooth.BluetoothDevice;
20import android.util.Log;
21
22import java.util.List;
23import java.util.ArrayList;
24import 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 */
43public class BluetoothDevicesInfo {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080044 private static final String TAG = "CarBluetoothDevicesInfo";
45 private static final boolean DBG = false;
46 private final int DEVICE_NOT_FOUND = -1;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080047
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 Periathiruvadiee28c002017-02-07 21:35:01 -080075 * 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 Periathiruvadi7ed84182017-01-20 15:18:08 -0800117 * 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 Periathiruvadiee28c002017-02-07 21:35:01 -0800131 if (checkDeviceInList(dev)) {
132 if (DBG) {
133 Log.d(TAG, "Device " + dev.getName() + "already in list. Not adding");
134 }
135 return;
136 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800137 if (mDeviceList != null) {
138 mDeviceList.add(dev);
139 } else {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800140 if (DBG) {
141 Log.d(TAG, "Device List is null");
142 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800143 }
144 if (mConnectionInfo != null) {
145 mConnectionInfo.mNumPairedDevices++;
146 } else {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800147 if (DBG) {
148 Log.d(TAG, "ConnectionInfo is null");
149 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800150 }
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 Periathiruvadiee28c002017-02-07 21:35:01 -0800161 } 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 Periathiruvadi7ed84182017-01-20 15:18:08 -0800172 }
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 Periathiruvadiee28c002017-02-07 21:35:01 -0800183 if (DBG) {
184 Log.d(TAG, "Getting device " + mConnectionInfo.mDeviceIndex + " from list");
185 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800186 if (mConnectionInfo.mDeviceIndex >= mConnectionInfo.mNumPairedDevices) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800187 if (DBG) {
188 Log.d(TAG,
189 "No device available for profile "
190 + mConnectionInfo.mProfile + " "
191 + mConnectionInfo.mNumPairedDevices);
192 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800193 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 Periathiruvadiee28c002017-02-07 21:35:01 -0800205 * @param device - {@link BluetoothDevice} that connected.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800206 * @param success - connection result
207 * @param retry - If Retries are available for the same device.
208 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800209 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 Periathiruvadi7ed84182017-01-20 15:18:08 -0800215 mConnectionInfo.mIsConnected = success;
216 if (success) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800217 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 Periathiruvadi7ed84182017-01-20 15:18:08 -0800253 mConnectionInfo.mDeviceIndex = 0;
254 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800255 // Reset the retry count
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800256 mConnectionInfo.mRetryAttempt = 0;
257 } else {
258 // if no more retries, move to the next device
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800259 if (DBG) {
260 Log.d(TAG, "Connection fail or Disconnected");
261 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800262 if (!retry) {
263 mConnectionInfo.mDeviceIndex++;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800264 if (DBG) {
265 Log.d(TAG, "Moving to device: " + mConnectionInfo.mDeviceIndex);
266 }
267 // Reset the retry count
268 mConnectionInfo.mRetryAttempt = 0;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800269 } else {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800270 if (DBG) {
271 Log.d(TAG, "Staying with the same device - retrying: "
272 + mConnectionInfo.mDeviceIndex);
273 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800274 }
275 }
276 }
277
278 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800279 * Move the item in the given position to the front of the list and push the rest down.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800280 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800281 * @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 Periathiruvadi7ed84182017-01-20 15:18:08 -0800299 */
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 Periathiruvadiee28c002017-02-07 21:35:01 -0800327 if (mConnectionInfo != null) {
328 mConnectionInfo.mRetryAttempt++;
329 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800330 }
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 Periathiruvadiee28c002017-02-07 21:35:01 -0800372 public void resetDeviceListLocked() {
373 if (mDeviceList != null) {
374 mDeviceList.clear();
375 mConnectionInfo.mNumPairedDevices = 0;
376 }
377 resetConnectionInfoLocked();
378 }
379
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800380}