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
17package com.android.car;
18
19import android.annotation.Nullable;
20import android.bluetooth.BluetoothDevice;
21import android.car.CarBluetoothManager;
22import android.util.Log;
23
24import java.util.List;
25import java.util.ArrayList;
26
27import android.bluetooth.BluetoothProfile;
28
29import com.android.internal.annotations.VisibleForTesting;
30
31/**
32 * BluetoothDevicesInfo contains all the information pertinent to connection on a Bluetooth Profile.
33 * It holds
34 * 1. a list of devices {@link #mDeviceInfoList} that has previously paired and connected on this
35 * profile.
36 * 2. a Connection Info object {@link #mConnectionInfo} that has following book keeping information:
37 * a) profile
38 * b) Current Connection status
39 * c) If there are any devices available for connection
40 * d) Index of the Device list that a connection is being tried upon currently.
41 * e) Number of devices that have been previously paired and connected on this profile.
42 * f) How many retry attempts have been made
43 *
44 * This is used by the {@link BluetoothDeviceConnectionPolicy} to find the device to attempt
45 * a connection on for a profile.  The policy also updates this object with the connection
46 * results.
47 */
48public class BluetoothDevicesInfo {
49
50    private static final String TAG = "CarBluetoothDevicesInfo";
51    private static final boolean DBG = Utils.DBG;
52    private final int DEVICE_NOT_FOUND = -1;
53    private final int DEVICE_PRIORITY_UNDEFINED = -1;
54    // The device list and the connection state information together have all the information
55    // that is required to know which device(s) to connect to, when we need to connect/
56    private List<DeviceInfo> mDeviceInfoList;
57    private ConnectionInfo mConnectionInfo;
58
59    /**
60     * This class holds on to information regarding this bluetooth profile's connection state.
61     */
62    private class ConnectionInfo {
63        // which bluetooth profile this Device Info is for
64        private int mProfile;
65        // are there any devices available to connect. It is false either if
66        // 1. no device has been paired to connect on this profile
67        // 2. all paired devices have been tried to connect to, but unsuccessful (not in range etc)
68        private boolean mDeviceAvailableToConnect;
69        // index of device in the mDeviceInfoList that the next connection attempt should be made
70        private int mDeviceIndex;
71        // Connection Retry counter
72        private int mRetryAttempt;
73        // Current number of active connections on this profile
74        private int mNumActiveConnections;
75        // number of concurrent active connections supported.
76        private int mNumConnectionsSupported;
77
78        public ConnectionInfo(int profile) {
79            // Default the number of concurrent active connections supported to 1.
80            this(profile, 1);
81        }
82
83        public ConnectionInfo(int profile, int numConnectionsSupported) {
84            mProfile = profile;
85            mNumConnectionsSupported = numConnectionsSupported;
86            initConnectionInfo();
87        }
88
89        private void initConnectionInfo() {
90            mDeviceAvailableToConnect = true;
91            mDeviceIndex = 0;
92            mRetryAttempt = 0;
93            mNumActiveConnections = 0;
94        }
95    }
96
97    /**
98     * This class holds information about the list of devices that can connect (have connected in
99     * the past) and their current connection state.
100     */
101    public class DeviceInfo {
102
103        private BluetoothDevice mBluetoothDevice;
104        private int mConnectionState;
105        private int mDevicePriority;
106
107        public DeviceInfo(BluetoothDevice device, int state) {
108            mBluetoothDevice = device;
109            mConnectionState = state;
110            mDevicePriority = DEVICE_PRIORITY_UNDEFINED;
111        }
112
113        public void setConnectionState(int state) {
114            mConnectionState = state;
115        }
116
117        public int getConnectionState() {
118            return mConnectionState;
119        }
120
121        public BluetoothDevice getBluetoothDevice() {
122            return mBluetoothDevice;
123        }
124
125        public void setBluetoothDevicePriority(int priority) {
126            mDevicePriority = priority;
127        }
128
129        public int getBluetoothDevicePriority() {
130            return mDevicePriority;
131        }
132    }
133
134    public BluetoothDevicesInfo(int profile) {
135        mDeviceInfoList = new ArrayList<>();
136        mConnectionInfo = new ConnectionInfo(profile);
137    }
138
139    public BluetoothDevicesInfo(int profile, int numConnectionsSupported) {
140        mDeviceInfoList = new ArrayList<>();
141        mConnectionInfo = new ConnectionInfo(profile, numConnectionsSupported);
142    }
143
144    /**
145     * Set the priority of the device with the given priority level
146     *
147     * @param deviceToTag - BluetoothDevice to set the priority for
148     * @param priority    - Priority to set
149     */
150
151    public void setBluetoothDevicePriorityLocked(BluetoothDevice deviceToTag, int priority) {
152        /*if (priority >= mConnectionInfo.mNumConnectionsSupported) {
153            if (DBG) {
154                Log.d(TAG, "Priority cannot exceed number of connections supported");
155            }
156            return;
157        }*/
158        // if there is a device already set to that priority, unseat that device
159        BluetoothDevice oldDeviceWithPriority = getBluetoothDeviceForPriorityLocked(priority);
160        if (oldDeviceWithPriority != null) {
161            if (DBG) {
162                Log.d(TAG, "Unsetting priority " + priority + " on " + oldDeviceWithPriority);
163            }
164            removeBluetoothDevicePriorityLocked(oldDeviceWithPriority);
165        }
166        // Tag the new device with the given priority
167        DeviceInfo newDeviceInfoWithPriority = findDeviceInfoInListLocked(deviceToTag);
168        if (newDeviceInfoWithPriority == null) {
169            if (DBG) {
170                Log.d(TAG, "setBluetoothDevicePriorityLocked():Unknown and unpaired device");
171            }
172            return;
173        }
174        if (DBG) {
175            Log.d(TAG, "Setting priority " + priority + " to "
176                    + newDeviceInfoWithPriority.mBluetoothDevice);
177        }
178        newDeviceInfoWithPriority.setBluetoothDevicePriority(priority);
179        // Update the position of the device in the device Queue
180        moveDeviceToPrioritySlotsLocked(newDeviceInfoWithPriority, priority);
181    }
182
183    /**
184     * Clear the priority of the given device.
185     *
186     * @param deviceToUntag - BluetoothDevice to untag
187     */
188    public void removeBluetoothDevicePriorityLocked(BluetoothDevice deviceToUntag) {
189        DeviceInfo deviceInfo = findDeviceInfoInListLocked(deviceToUntag);
190        deviceInfo.setBluetoothDevicePriority(DEVICE_PRIORITY_UNDEFINED);
191    }
192
193    /**
194     * Returns the number of devices that have been tagged as priority devices.
195     * If there is a device that is tagged as a Secondary device, then the number of tagged devices
196     * is 2, even if there is no primary device.
197     *
198     * @return - Number of Tagged devices Ex: Only Primary - 1, Primary and/or Secondary - 2
199     */
200    public int getNumberOfTaggedDevicesLocked() {
201        int numberOfTaggedDevices = 0;
202        if (getBluetoothDeviceForPriorityLocked(
203                CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1) != null) {
204            return CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1 + 1;
205        } else if (getBluetoothDeviceForPriorityLocked(
206                CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) != null) {
207            return CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0 + 1;
208        }
209        return numberOfTaggedDevices;
210    }
211
212    /**
213     * Returns the device that has the passed priority
214     */
215    public BluetoothDevice getBluetoothDeviceForPriorityLocked(int priority) {
216        BluetoothDevice device = null;
217        for (DeviceInfo deviceInfo : mDeviceInfoList) {
218            if (deviceInfo.mDevicePriority == priority) {
219                return deviceInfo.mBluetoothDevice;
220            }
221        }
222        return device;
223    }
224
225    /**
226     * Get the position of the given device in the list of connectable devices for this profile.
227     *
228     * @param device - {@link BluetoothDevice}
229     * @return postion in the {@link #mDeviceInfoList}, DEVICE_NOT_FOUND if the device is not in the
230     * list.
231     */
232    private int getPositionInListLocked(BluetoothDevice device) {
233        int index = DEVICE_NOT_FOUND;
234        if (mDeviceInfoList != null) {
235            int i = 0;
236            for (DeviceInfo devInfo : mDeviceInfoList) {
237                if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) {
238                    index = i;
239                    break;
240                }
241                i++;
242            }
243        }
244        return index;
245    }
246
247    /**
248     * Check if the given device is in the {@link #mDeviceInfoList}
249     *
250     * @param device - {@link BluetoothDevice} to look for
251     * @return true if found, false if not found
252     */
253    boolean checkDeviceInListLocked(BluetoothDevice device) {
254        boolean isPresent = false;
255        if (device == null) {
256            return isPresent;
257        }
258        for (DeviceInfo devInfo : mDeviceInfoList) {
259            if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) {
260                isPresent = true;
261                break;
262            }
263        }
264        return isPresent;
265    }
266
267    /**
268     * Iterate through the {@link BluetoothDevicesInfo#mDeviceInfoList} and find the
269     * {@link DeviceInfo} with the given {@link BluetoothDevice}
270     *
271     * @param device - {@link BluetoothDevice} to look for
272     * @return - {@link DeviceInfo} that contains the passed {@link BluetoothDevice}
273     */
274    private DeviceInfo findDeviceInfoInListLocked(@Nullable BluetoothDevice device) {
275        if (device == null) {
276            return null;
277        }
278        for (DeviceInfo devInfo : mDeviceInfoList) {
279            if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) {
280                return devInfo;
281            }
282        }
283        return null;
284    }
285
286    /**
287     * Get the current list of connectable devices for this profile.
288     *
289     * @return Device list for this profile.
290     */
291    public List<BluetoothDevice> getDeviceList() {
292        List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>();
293        for (DeviceInfo deviceInfo : mDeviceInfoList) {
294            bluetoothDeviceList.add(deviceInfo.mBluetoothDevice);
295        }
296        return bluetoothDeviceList;
297    }
298
299    public List<DeviceInfo> getDeviceInfoList() {
300        return mDeviceInfoList;
301    }
302
303    public void setNumberOfConnectionsSupported(int num) {
304        mConnectionInfo.mNumConnectionsSupported = num;
305    }
306
307    public int getNumberOfConnectionsSupported() {
308        return mConnectionInfo.mNumConnectionsSupported;
309    }
310
311    /**
312     * Add a device to the device list.  Used during pairing.
313     *
314     * @param dev - device to add for further connection attempts on this profile.
315     */
316    public void addDeviceLocked(BluetoothDevice dev) {
317        // Check if this device is already in the device list
318        if (checkDeviceInListLocked(dev)) {
319            if (DBG) {
320                Log.d(TAG, "Device " + dev + " already in list.  Not adding");
321            }
322            return;
323        }
324        // Add new device and set the connection state to DISCONNECTED.
325        if (mDeviceInfoList != null) {
326            DeviceInfo deviceInfo = new DeviceInfo(dev, BluetoothProfile.STATE_DISCONNECTED);
327            mDeviceInfoList.add(deviceInfo);
328        } else {
329            if (DBG) {
330                Log.d(TAG, "Device List is null");
331            }
332        }
333    }
334
335    /**
336     * Set the connection state for this device to the given connection state.
337     *
338     * @param device - Bluetooth device to update the state for
339     * @param state  - the Connection state to set.
340     */
341    public void setConnectionStateLocked(BluetoothDevice device, int state) {
342        if (device == null) {
343            Log.e(TAG, "setConnectionStateLocked() device null");
344            return;
345        }
346        for (DeviceInfo devInfo : mDeviceInfoList) {
347            BluetoothDevice dev = devInfo.mBluetoothDevice;
348            if (dev == null) {
349                continue;
350            }
351            if (dev.getAddress().equals(device.getAddress())) {
352                if (DBG) {
353                    Log.d(TAG, "Setting " + dev + " state to " + state);
354                }
355                devInfo.setConnectionState(state);
356                break;
357            }
358        }
359    }
360
361    /**
362     * Returns the current connection state for the given device
363     *
364     * @param device - device to get the bluetooth connection state for
365     * @return - Connection State.  If passed device is null, returns DEVICE_NOT_FOUND.
366     */
367    public int getCurrentConnectionStateLocked(BluetoothDevice device) {
368        int state = DEVICE_NOT_FOUND;
369        if (device == null) {
370            Log.e(TAG, "getCurrentConnectionStateLocked() device null");
371            return state;
372        }
373
374        for (DeviceInfo devInfo : mDeviceInfoList) {
375            BluetoothDevice dev = devInfo.mBluetoothDevice;
376            if (dev == null) {
377                continue;
378            }
379            if (dev.getAddress().equals(device.getAddress())) {
380                state = devInfo.getConnectionState();
381                break;
382            }
383        }
384        return state;
385    }
386
387    /**
388     * Returns the device that is currently in the middle of a connection attempt.
389     *
390     * @return BluetoothDevice that is connecting, null if no device is connecting
391     */
392    public BluetoothDevice getConnectingDeviceLocked() {
393        for (DeviceInfo devInfo : mDeviceInfoList) {
394            if (devInfo.getConnectionState() == BluetoothProfile.STATE_CONNECTING) {
395                return devInfo.getBluetoothDevice();
396            }
397        }
398        return null;
399    }
400
401    /**
402     * Returns a list of connected devices for this profile.
403     *
404     * @return - List of connected devices
405     */
406    public List<BluetoothDevice> getConnectedDevicesLocked() {
407        List<BluetoothDevice> devices = new ArrayList<>();
408        for (DeviceInfo devInfo : mDeviceInfoList) {
409            if (devInfo.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
410                devices.add(devInfo.getBluetoothDevice());
411            }
412        }
413        if (DBG) {
414            Log.d(TAG, "Active Connections: " + getNumberOfActiveConnectionsLocked());
415            Log.d(TAG, "Connected devices Size: " + devices.size());
416        }
417        return devices;
418    }
419
420    /**
421     * Remove a device from the list.  Used when a device is unpaired
422     *
423     * @param dev - device to remove from the list.
424     */
425    public void removeDeviceLocked(BluetoothDevice dev) {
426        if (mDeviceInfoList != null) {
427            DeviceInfo devInfo = findDeviceInfoInListLocked(dev);
428            if (devInfo != null) {
429                mDeviceInfoList.remove(devInfo);
430                // If the device was connected when it was unpaired, we wouldn't have received the
431                // Profile disconnected intents.  Hence check if the device was connected and if it
432                // was, then decrement the number of active connections.
433                if (devInfo.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
434                    mConnectionInfo.mNumActiveConnections--;
435                }
436            }
437        } else {
438            if (DBG) {
439                Log.d(TAG, "Device List is null");
440            }
441        }
442        Log.d(TAG, "Device List size: " + mDeviceInfoList.size());
443    }
444
445    public void clearDeviceListLocked() {
446        if (mDeviceInfoList != null) {
447            mDeviceInfoList.clear();
448        }
449    }
450
451    /**
452     * Returns the next device to attempt a connection on for this profile.
453     *
454     * @return {@link BluetoothDevice} that is next in the Queue. null if the Queue has been
455     * exhausted
456     * (no known device nearby)
457     */
458    public BluetoothDevice getNextDeviceInQueueLocked() {
459        BluetoothDevice device = null;
460        int numberOfPairedDevices = getNumberOfPairedDevicesLocked();
461        // iterate till we find a disconnected device
462        while (mConnectionInfo.mDeviceIndex < numberOfPairedDevices &&
463                mDeviceInfoList.get(mConnectionInfo.mDeviceIndex).getConnectionState()
464                        != BluetoothProfile.STATE_DISCONNECTED) {
465            mConnectionInfo.mDeviceIndex++;
466        }
467
468        if (mConnectionInfo.mDeviceIndex >= numberOfPairedDevices) {
469            if (DBG) {
470                Log.d(TAG,
471                        "No device available for profile "
472                                + mConnectionInfo.mProfile + " "
473                                + mConnectionInfo.mDeviceIndex + "/"
474                                + numberOfPairedDevices);
475            }
476            // mDeviceIndex is the index of the device in the mDeviceInfoList, that the next
477            // connection attempt would be made on.  It is moved ahead on
478            // updateConnectionStatusLocked() so it always holds the index of the next device to
479            // connect to.  But here, when we get the next device to connect to, if we see that
480            // the index is greater than the number of devices in the list, then we move the index
481            // back to the first device in the list and don't return anything.
482            // The reason why this is reset is to imply that connection attempts on this profile has
483            // been exhausted and if you want to retry connecting on this profile, we will start
484            // from the first device.
485            // The reason to reset here rather than in updateConnectionStatusLocked() is to make
486            // sure we have the latest view of the numberOfPairedDevices before we say we have
487            // exhausted the list.
488            mConnectionInfo.mDeviceIndex = 0;
489            return null;
490        }
491
492        device = mDeviceInfoList.get(mConnectionInfo.mDeviceIndex).mBluetoothDevice;
493        if (DBG) {
494            Log.d(TAG, "Getting device " + mConnectionInfo.mDeviceIndex + " from list: "
495                    + device);
496        }
497        return device;
498    }
499
500    /**
501     * Update the connection Status for connection attempts made on this profile.
502     * If the attempt was successful, mark it and keep track of the device that was connected.
503     * If unsuccessful, check if we can retry on the same device. If no more retry attempts,
504     * move to the next device in the Queue.
505     *
506     * @param device  - {@link BluetoothDevice} that connected.
507     * @param success - connection result
508     * @param retry   - If Retries are available for the same device.
509     */
510    public void updateConnectionStatusLocked(BluetoothDevice device, boolean success,
511            boolean retry) {
512        if (device == null) {
513            Log.w(TAG, "Updating Status with null BluetoothDevice");
514            return;
515        }
516        if (success) {
517            if (DBG) {
518                Log.d(TAG, mConnectionInfo.mProfile + " connected to " + device);
519            }
520            // Get the position of this device in the device list maintained for this profile.
521            int positionInQ = getPositionInListLocked(device);
522            if (DBG) {
523                Log.d(TAG, "Position of " + device + " in Q: " + positionInQ);
524            }
525            // If the device that connected is not in the list, it could be because it is being
526            // paired and getting added to the device list for this profile for the first time.
527            if (positionInQ == DEVICE_NOT_FOUND) {
528                Log.d(TAG, "Connected device not in Q: " + device);
529                addDeviceLocked(device);
530                positionInQ = mDeviceInfoList.size() - 1;
531            } else if (positionInQ != mConnectionInfo.mDeviceIndex) {
532            /*
533                 This will happen if auto-connect requests to connect on a device from its list,
534                 but the device that connected was different.  Maybe there was another requestor
535                 and the Bluetooth services chose to honor the other request.  What we do here,
536                 is to make sure we note which device connected and not assume that the device
537                 that connected is the device we requested.  The ultimate goal of the policy is
538                 to remember which devices connected on which profile (regardless of the origin
539                 of the connection request) so it knows which device to connect the next time.
540            */
541                if (DBG) {
542                    Log.d(TAG, "Different device connected: " + device + " CurrIndex: "
543                            + mConnectionInfo.mDeviceIndex);
544                }
545            }
546
547            // At this point positionInQ reflects where in the list the device that connected is,
548            // i.e, its index.  Move the device to the front of the device list, since the policy is
549            // to try to connect to the last connected device first.  Hence by moving the device
550            // to the front of the list, the next time auto connect triggers, this will be the
551            // device that the policy will try to connect on for this profile.
552            if (positionInQ != 0) {
553                moveDeviceToQueueFrontLocked(positionInQ);
554                // reset the device Index back to the first in the Queue
555                //mConnectionInfo.mDeviceIndex = 0;
556            }
557
558            if (getCurrentConnectionStateLocked(device) != BluetoothProfile.STATE_CONNECTED) {
559                mConnectionInfo.mNumActiveConnections++;
560                if (DBG) {
561                    Log.d(TAG,
562                            "Incrementing Active Connections "
563                                    + mConnectionInfo.mNumActiveConnections);
564                }
565            }
566            setConnectionStateLocked(device, BluetoothProfile.STATE_CONNECTED);
567            // Reset the retry count
568            mConnectionInfo.mRetryAttempt = 0;
569
570            if (getConnectingDeviceLocked() == null) {
571                mConnectionInfo.mDeviceIndex++;
572            }
573            if (DBG) {
574                Log.d(TAG,
575                        "Profile: " + mConnectionInfo.mProfile + " Number of Active Connections: "
576                                + mConnectionInfo.mNumActiveConnections);
577            }
578        } else {
579            // if no more retries, move to the next device
580            if (DBG) {
581                Log.d(TAG, "Connection fail or Disconnected");
582            }
583            // Decrement Number of Active Connections only if the device is already connected.
584            if (getCurrentConnectionStateLocked(device) == BluetoothProfile.STATE_CONNECTED) {
585                mConnectionInfo.mNumActiveConnections--;
586                if (DBG) {
587                    Log.d(TAG, "Decrementing Active Connections "
588                            + mConnectionInfo.mNumActiveConnections);
589                }
590            }
591            setConnectionStateLocked(device, BluetoothProfile.STATE_DISCONNECTED);
592
593            // Update the mDeviceIndex only when there is no other device currently in the middle
594            // of a connection attempt.  This is to safeguard against disconnect intents coming in
595            // for devices other than the one that the policy is currently trying to connect.
596            if (getConnectingDeviceLocked() == null) {
597                if (!retry) {
598                    mConnectionInfo.mDeviceIndex++;
599                    if (DBG) {
600                        Log.d(TAG, "Moving to device: " + mConnectionInfo.mDeviceIndex);
601                    }
602                    // Reset the retry count
603                    mConnectionInfo.mRetryAttempt = 0;
604                } else {
605                    if (DBG) {
606                        Log.d(TAG, "Staying with the same device - retrying: "
607                                + mConnectionInfo.mDeviceIndex);
608                    }
609                }
610            } else {
611                BluetoothDevice connectingDevice = getConnectingDeviceLocked();
612                if (connectingDevice != null) {
613                    if (DBG) {
614                        Log.d(TAG, "Not moving to next device. " + connectingDevice
615                                + " still connecting");
616                    }
617                } else {
618                    Log.e(TAG, "Unexpected. Status = connecting, but connecting device = null");
619                }
620            }
621        }
622    }
623
624    /**
625     * Move the given device to its priority slot
626     *
627     * @param deviceInfo - DeviceInfo to move
628     * @param priority   - Priority of the device in the list
629     */
630    private void moveDeviceToPrioritySlotsLocked(DeviceInfo deviceInfo, int priority) {
631        if (DBG) {
632            Log.d(TAG, "Moving " + deviceInfo.mBluetoothDevice + " to " + priority);
633        }
634        mDeviceInfoList.remove(deviceInfo);
635        mDeviceInfoList.add(priority, deviceInfo);
636    }
637
638    /**
639     * Move the item in the given position to the front of the queue and push the rest down.
640     *
641     * @param position - current position of the device that it is moving from
642     */
643    private void moveDeviceToQueueFrontLocked(int position) {
644        int topOfList = getNumberOfTaggedDevicesLocked();
645        // If the device is a primary or secondary, its position is fixed.
646        if (position <= topOfList) {
647            return;
648        }
649        DeviceInfo deviceInfo = mDeviceInfoList.get(position);
650        if (deviceInfo.mBluetoothDevice == null) {
651            if (DBG) {
652                Log.d(TAG, "Unexpected: deviceToMove is null");
653            }
654            return;
655        }
656        mDeviceInfoList.remove(position);
657        // Top of the list to which a device can be moved depends on the number of tagged devices
658        // If there is a dedicated Primary device, then the newly connected device can only be moved
659        // to the second position, since the primary device always occupies the first position.
660        // Hence the topOfList is the first position after the tagged devices.
661        mDeviceInfoList.add(topOfList, deviceInfo);
662    }
663
664    /**
665     * Returns the profile that this devicesInfo is for.
666     */
667    public Integer getProfileLocked() {
668        return mConnectionInfo.mProfile;
669    }
670
671    /**
672     * Get the number of devices in the {@link #mDeviceInfoList} - paired and previously connected
673     * devices
674     *
675     * @return number of paired devices on this profile.
676     */
677    public int getNumberOfPairedDevicesLocked() {
678        return mDeviceInfoList.size();
679    }
680
681    /**
682     * Increment the retry count. Called when a connection is made on the profile.
683     */
684    public void incrementRetryCountLocked() {
685        if (mConnectionInfo != null) {
686            mConnectionInfo.mRetryAttempt++;
687        }
688    }
689
690    /**
691     * Get the number of times a connection attempt has been tried on a device for this profile.
692     *
693     * @return number of retry attempts.
694     */
695    public Integer getRetryCountLocked() {
696        return mConnectionInfo.mRetryAttempt;
697    }
698
699    /**
700     * Set the mDeviceAvailableToConnect with the passed value.
701     *
702     * @param deviceAvailable - true or false.
703     */
704    public void setDeviceAvailableToConnectLocked(boolean deviceAvailable) {
705        mConnectionInfo.mDeviceAvailableToConnect = deviceAvailable;
706    }
707
708    /**
709     * Returns if there are any devices available to connect on this profile.
710     *
711     * @return true if a device is available, false
712     * 1. if number of active connections on this profile has been maxed out or
713     * 2. if all devices in the list have failed to connect already.
714     */
715    public boolean isProfileConnectableLocked() {
716        if (DBG) {
717            Log.d(TAG, "Profile: " + mConnectionInfo.mProfile + " Num of connections: "
718                    + mConnectionInfo.mNumActiveConnections + " Conn Supported: "
719                    + mConnectionInfo.mNumConnectionsSupported
720                    + ", the flag: mDeviceAvailableToConnect = "
721                    + mConnectionInfo.mDeviceAvailableToConnect);
722        }
723
724        if (mConnectionInfo.mDeviceAvailableToConnect &&
725                mConnectionInfo.mNumActiveConnections < mConnectionInfo.mNumConnectionsSupported) {
726            return true;
727        }
728
729        if (DBG) {
730            if (mConnectionInfo.mDeviceAvailableToConnect) {
731                Log.d(TAG, "Connected devices for profile " + mConnectionInfo.mProfile);
732                for (BluetoothDevice device : getConnectedDevicesLocked()) {
733                    Log.d(TAG, device.getAddress());
734                }
735            }
736        }
737        return false;
738    }
739
740    /**
741     * Return the current number of active connections on this profile.
742     *
743     * @return number of active connections.
744     */
745    public int getNumberOfActiveConnectionsLocked() {
746        return mConnectionInfo.mNumActiveConnections;
747    }
748
749    public void resetDeviceIndex() {
750        mConnectionInfo.mDeviceIndex = 0;
751    }
752
753    /**
754     * Reset the connection related bookkeeping information.
755     * Called on a BluetoothAdapter Off to clean slate
756     */
757    public void resetConnectionInfoLocked() {
758        mConnectionInfo.mNumActiveConnections = 0;
759        mConnectionInfo.mDeviceIndex = 0;
760        mConnectionInfo.mRetryAttempt = 0;
761        mConnectionInfo.mDeviceAvailableToConnect = true;
762        for (DeviceInfo info : mDeviceInfoList) {
763            setConnectionStateLocked(info.getBluetoothDevice(),
764                    BluetoothProfile.STATE_DISCONNECTED);
765        }
766    }
767
768    public void resetDeviceListLocked() {
769        if (mDeviceInfoList != null) {
770            mDeviceInfoList.clear();
771        }
772        resetConnectionInfoLocked();
773    }
774
775    @VisibleForTesting
776    String toDebugString() {
777        StringBuilder buf = new StringBuilder();
778        buf.append("\tmDeviceAvailableToConnect = " + mConnectionInfo.mDeviceAvailableToConnect);
779        buf.append("\n\t#Paired Devices = " + getNumberOfPairedDevicesLocked());
780        buf.append(", #Active Connections = " + mConnectionInfo.mNumActiveConnections);
781        buf.append(", #Connections Supported = " + mConnectionInfo.mNumConnectionsSupported);
782        if (mDeviceInfoList != null) {
783            int i = 1;
784            for (DeviceInfo devInfo : mDeviceInfoList) {
785                buf.append("\n\t\tdevice# " + i++ +
786                        " = " + Utils.getDeviceDebugInfo(devInfo.getBluetoothDevice()));
787                String s;
788                switch (devInfo.getConnectionState()) {
789                    case BluetoothProfile.STATE_CONNECTING:
790                        s = "STATE_CONNECTING";
791                        break;
792                    case BluetoothProfile.STATE_DISCONNECTED:
793                        s = "STATE_DISCONNECTED";
794                        break;
795                    case BluetoothProfile.STATE_CONNECTED:
796                        s = "STATE_CONNECTED";
797                        break;
798                    default:
799                        s = "state_UNKNOWN";
800                }
801                buf.append(", " + s);
802            }
803        } else {
804            buf.append("\n\tno devices listed");
805        }
806        buf.append("\n");
807        return buf.toString();
808    }
809}
810