• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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