blob: eb77aad8088731984072e5d733ae9b784b301ac9 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car;
import android.bluetooth.BluetoothDevice;
import android.util.Log;
import java.util.List;
import java.util.ArrayList;
import android.bluetooth.BluetoothProfile;
/**
* BluetoothDevicesInfo contains all the information pertinent to connection on a Bluetooth Profile.
* It holds
* 1. a list of devices {@link #mDeviceList} that has previously paired and connected on this
* profile.
* 2. a Connection Info object {@link #mConnectionInfo} that has following book keeping information:
* a) profile
* b) Current Connection status
* c) If there are any devices available for connection
* d) Index of the Device list that a connection is being tried upon currently.
* e) Number of devices that have been previously paired and connected on this profile.
* f) How many retry attempts have been made
*
* This is used by the {@link BluetoothDeviceConnectionPolicy} to find the device to attempt
* a connection on for a profile. The policy also updates this object with the connection
* results.
*/
public class BluetoothDevicesInfo {
private static final String TAG = "CarBluetoothDevicesInfo";
private static final boolean DBG = false;
private final int DEVICE_NOT_FOUND = -1;
// The device list and the connection state information together have all the information
// that is required to know which device(s) to connect to, when we need to connect/
private List<DeviceInfo> mDeviceInfoList;
private ConnectionInfo mConnectionInfo;
/**
* This class holds on to information regarding this bluetooth profile's connection state.
*/
private class ConnectionInfo {
int mProfile; // which bluetooth profile this Device Info is for
boolean mDeviceAvailableToConnect; // known devices available to connect on this profile.
int mDeviceIndex; // position of this device in the device List.
int mNumPairedDevices; // Number of paired & connected devices for this profile
int mRetryAttempt; // Connection Retry counter
int mNumActiveConnections; // Current number of active connections on this profile
int mNumConnectionsSupported; // number of concurrent active connections supported.
public ConnectionInfo(int profile) {
// Default the number of concurrent active connections supported to 1.
this(profile, 1);
}
public ConnectionInfo(int profile, int numConnectionsSupported) {
mProfile = profile;
mNumConnectionsSupported = numConnectionsSupported;
initConnectionInfo();
}
private void initConnectionInfo() {
mDeviceAvailableToConnect = true;
mDeviceIndex = 0;
mNumPairedDevices = 0;
mRetryAttempt = 0;
mNumActiveConnections = 0;
}
}
/**
* This class holds information about the list of devices that can connect (have connected in
* the past) and their current connection state.
*/
public class DeviceInfo {
private BluetoothDevice mBluetoothDevice;
private int mConnectionState;
public DeviceInfo(BluetoothDevice device, int state) {
mBluetoothDevice = device;
mConnectionState = state;
}
public void setConnectionState(int state) {
mConnectionState = state;
}
public int getConnectionState() {
return mConnectionState;
}
public BluetoothDevice getBluetoothDevice() {
return mBluetoothDevice;
}
}
public BluetoothDevicesInfo(int profile) {
mDeviceInfoList = new ArrayList<>();
mConnectionInfo = new ConnectionInfo(profile);
}
public BluetoothDevicesInfo(int profile, int numConnectionsSupported) {
mDeviceInfoList = new ArrayList<>();
mConnectionInfo = new ConnectionInfo(profile, numConnectionsSupported);
}
/**
* Get the position of the given device in the list of connectable devices for this profile.
*
* @param device - {@link BluetoothDevice}
* @return postion in the {@link #mDeviceInfoList}, DEVICE_NOT_FOUND if the device is not in the
* list.
*/
private int getPositionInListLocked(BluetoothDevice device) {
int index = DEVICE_NOT_FOUND;
if (mDeviceInfoList != null) {
int i = 0;
for (DeviceInfo devInfo : mDeviceInfoList) {
if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) {
index = i;
break;
}
i++;
}
}
return index;
}
/**
* Check if the given device is in the {@link #mDeviceInfoList}
*
* @param device - {@link BluetoothDevice} to look for
* @return true if found, false if not found
*/
private boolean checkDeviceInListLocked(BluetoothDevice device) {
boolean isPresent = false;
if (device == null) {
return isPresent;
}
for (DeviceInfo devInfo : mDeviceInfoList) {
if (devInfo.mBluetoothDevice.getAddress().equals(device.getAddress())) {
isPresent = true;
break;
}
}
return isPresent;
}
/**
* Get the current list of connectable devices for this profile.
*
* @return Device list for this profile.
*/
public List<BluetoothDevice> getDeviceList() {
List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>();
for (DeviceInfo deviceInfo : mDeviceInfoList) {
bluetoothDeviceList.add(deviceInfo.mBluetoothDevice);
}
return bluetoothDeviceList;
}
public List<DeviceInfo> getDeviceInfoList() {
return mDeviceInfoList;
}
public void setNumberOfConnectionsSupported(int num) {
mConnectionInfo.mNumConnectionsSupported = num;
}
public int getNumberOfConnectionsSupported() {
return mConnectionInfo.mNumConnectionsSupported;
}
/**
* Add a device to the device list. Used during pairing.
*
* @param dev - device to add for further connection attempts on this profile.
*/
public void addDeviceLocked(BluetoothDevice dev) {
// Check if this device is already in the device list
if (checkDeviceInListLocked(dev)) {
if (DBG) {
Log.d(TAG, "Device " + dev.getName() + "already in list. Not adding");
}
return;
}
// Add new device and set the connection state to DISCONNECTED.
if (mDeviceInfoList != null) {
DeviceInfo deviceInfo = new DeviceInfo(dev, BluetoothProfile.STATE_DISCONNECTED);
mDeviceInfoList.add(deviceInfo);
} else {
if (DBG) {
Log.d(TAG, "Device List is null");
}
}
// Increment the number of paired devices for this profile.
if (mConnectionInfo != null) {
mConnectionInfo.mNumPairedDevices++;
} else {
if (DBG) {
Log.d(TAG, "Unexpected: ConnectionInfo is null");
}
}
}
/**
* Set the connection state for this device to the given connection state.
*
* @param device - Bluetooth device to update the state for
* @param state - the Connection state to set.
*/
public void setConnectionStateLocked(BluetoothDevice device, int state) {
if (device == null) {
Log.e(TAG, "setConnectionStateLocked() device null");
return;
}
for (DeviceInfo devInfo : mDeviceInfoList) {
BluetoothDevice dev = devInfo.mBluetoothDevice;
if (dev == null) {
continue;
}
if (dev.getAddress().equals(device.getAddress())) {
if (DBG) {
Log.d(TAG, "Setting " + dev.getName() + " state to " + state);
}
devInfo.setConnectionState(state);
break;
}
}
}
/**
* Returns the current connection state for the given device
*
* @param device - device to get the bluetooth connection state for
* @return - Connection State. If passed device is null, returns DEVICE_NOT_FOUND.
*/
public int getConnectionStateLocked(BluetoothDevice device) {
int state = DEVICE_NOT_FOUND;
if (device == null) {
Log.e(TAG, "getConnectionStateLocked() device null");
return state;
}
for (DeviceInfo devInfo : mDeviceInfoList) {
BluetoothDevice dev = devInfo.mBluetoothDevice;
if (dev == null) {
continue;
}
if (dev.getAddress().equals(device.getAddress())) {
state = devInfo.getConnectionState();
break;
}
}
return state;
}
/**
* Remove a device from the list. Used when a device is unpaired
*
* @param dev - device to remove from the list.
*/
public void removeDeviceLocked(BluetoothDevice dev) {
if (mDeviceInfoList != null) {
mDeviceInfoList.remove(dev);
} else {
if (DBG) {
Log.d(TAG, "Device List is null");
}
}
if (mConnectionInfo != null) {
mConnectionInfo.mNumPairedDevices--;
} else {
if (DBG) {
Log.d(TAG, "ConnectionInfo is null");
}
}
}
public void clearDeviceListLocked() {
if (mDeviceInfoList != null) {
mDeviceInfoList.clear();
}
}
/**
* Returns the next device to attempt a connection on for this profile.
*
* @return {@link BluetoothDevice} that is next in the Queue. null if the Queue has been
* exhausted
* (no known device nearby)
*/
public BluetoothDevice getNextDeviceInQueueLocked() {
BluetoothDevice device = null;
if (mConnectionInfo.mDeviceIndex >= mConnectionInfo.mNumPairedDevices) {
if (DBG) {
Log.d(TAG,
"No device available for profile "
+ mConnectionInfo.mProfile + " "
+ mConnectionInfo.mDeviceIndex + "/"
+ mConnectionInfo.mNumPairedDevices);
}
mConnectionInfo.mDeviceIndex = 0; //reset the index
return null;
}
device = mDeviceInfoList.get(mConnectionInfo.mDeviceIndex).mBluetoothDevice;
if (DBG) {
Log.d(TAG, "Getting device " + mConnectionInfo.mDeviceIndex + " from list: "
+ device.getName());
}
return device;
}
/**
* Update the connection Status for connection attempts made on this profile.
* If the attempt was successful, mark it and keep track of the device that was connected.
* If unsuccessful, check if we can retry on the same device. If no more retry attempts,
* move to the next device in the Queue.
*
* @param device - {@link BluetoothDevice} that connected.
* @param success - connection result
* @param retry - If Retries are available for the same device.
*/
public void updateConnectionStatusLocked(BluetoothDevice device, boolean success,
boolean retry) {
if (device == null) {
Log.w(TAG, "Updating Status with null BluetoothDevice");
return;
}
if (success) {
if (DBG) {
Log.d(TAG, mConnectionInfo.mProfile + " connected to " + device.getName());
}
// b/34722344 - TODO
// Get the position of this device in the device list maintained for this profile.
int positionInQ = getPositionInListLocked(device);
if (DBG) {
Log.d(TAG, "Position of " + device.getName() + " in Q: " + positionInQ);
}
// If the device that connected is not in the list, it could be because it is being
// paired and getting added to the device list for this profile for the first time.
if (positionInQ == DEVICE_NOT_FOUND) {
Log.d(TAG, "Connected device not in Q: " + device.getName());
addDeviceLocked(device);
positionInQ = mDeviceInfoList.size() - 1;
} else if (positionInQ != mConnectionInfo.mDeviceIndex) {
// This will happen if auto-connect request a connect on a device from its list,
// but the device that connected was different. Maybe there was another requestor
// and the Bluetooth services chose to honor the other request. What we do here,
// is to make sure we note which device connected and not assume that the device
// that connected is the device we requested. The ultimate goal of the policy is
// to remember which devices connected on which profile (regardless of the origin
// of the connection request) so it knows which device to connect the next time.
if (DBG) {
Log.d(TAG, "Different device connected: " + device.getName());
}
}
// At this point positionInQ reflects where in the list the device that connected is,
// i.e, its index. Move the device to the front of the device list, since the policy is
// to try to connect to the last connected device first. Hence by moving the device
// to the front of the list, the next time auto connect triggers, this will be the
// device that the policy will try to connect on for this profile.
if (positionInQ != 0) {
moveToFrontLocked(positionInQ);
// reset the device Index back to the first in the Queue
//mConnectionInfo.mDeviceIndex = 0;
}
setConnectionStateLocked(device, BluetoothProfile.STATE_CONNECTED);
// Reset the retry count
mConnectionInfo.mRetryAttempt = 0;
mConnectionInfo.mNumActiveConnections++;
mConnectionInfo.mDeviceIndex++;
if (DBG) {
Log.d(TAG,
"Profile: " + mConnectionInfo.mProfile + " Number of Active Connections: "
+ mConnectionInfo.mNumActiveConnections);
}
} else {
// if no more retries, move to the next device
if (DBG) {
Log.d(TAG, "Connection fail or Disconnected");
}
// only if the given device was already connected decrement this.
if (getConnectionStateLocked(device) == BluetoothProfile.STATE_CONNECTED) {
mConnectionInfo.mNumActiveConnections--;
}
setConnectionStateLocked(device, BluetoothProfile.STATE_DISCONNECTED);
if (!retry) {
mConnectionInfo.mDeviceIndex++;
if (DBG) {
Log.d(TAG, "Moving to device: " + mConnectionInfo.mDeviceIndex);
}
// Reset the retry count
mConnectionInfo.mRetryAttempt = 0;
} else {
if (DBG) {
Log.d(TAG, "Staying with the same device - retrying: "
+ mConnectionInfo.mDeviceIndex);
}
}
}
}
/**
* Move the item in the given position to the front of the list and push the rest down.
*
* @param position - position of the device to move from
*/
private void moveToFrontLocked(int position) {
DeviceInfo deviceInfo = mDeviceInfoList.get(position);
if (deviceInfo.mBluetoothDevice == null) {
if (DBG) {
Log.d(TAG, "Unexpected: deviceToMove is null");
}
return;
}
mDeviceInfoList.remove(position);
mDeviceInfoList.add(0, deviceInfo);
}
/**
* Returns the profile that this devicesInfo is for.
*/
public Integer getProfileLocked() {
return mConnectionInfo.mProfile;
}
/**
* Get the number of devices in the {@link #mDeviceInfoList} - paired and previously connected
* devices
*
* @return number of paired devices on this profile.
*/
public Integer getNumberOfPairedDevicesLocked() {
return mConnectionInfo.mNumPairedDevices;
}
/**
* Increment the retry count. Called when a connection is made on the profile.
*/
public void incrementRetryCountLocked() {
if (mConnectionInfo != null) {
mConnectionInfo.mRetryAttempt++;
}
}
/**
* Get the number of times a connection attempt has been tried on a device for this profile.
*
* @return number of retry attempts.
*/
public Integer getRetryCountLocked() {
return mConnectionInfo.mRetryAttempt;
}
/**
* Set the mDeviceAvailableToConnect with the passed value.
*
* @param deviceAvailable - true or false.
*/
public void setDeviceAvailableToConnectLocked(boolean deviceAvailable) {
mConnectionInfo.mDeviceAvailableToConnect = deviceAvailable;
}
/**
* Returns if there are any devices available to connect on this profile.
*
* @return true if a device is available, false
* 1. if number of active connections on this profile has been maxed out or
* 2. if all devices in the list have failed to connect already.
*/
public boolean isProfileConnectableLocked() {
if (DBG) {
Log.d(TAG, "Profile: " + mConnectionInfo.mProfile + " Num of connections: "
+ mConnectionInfo.mNumActiveConnections + " Conn Supported: "
+ mConnectionInfo.mNumConnectionsSupported);
}
if (mConnectionInfo.mDeviceAvailableToConnect &&
mConnectionInfo.mNumActiveConnections < mConnectionInfo.mNumConnectionsSupported) {
return true;
}
return false;
}
/**
* Return the current number of active connections on this profile.
*
* @return number of active connections.
*/
public int getNumberOfActiveConnectionsLocked() {
return mConnectionInfo.mNumActiveConnections;
}
/**
* Reset the connection related bookkeeping information.
* Called on a BluetoothAdapter Off to clean slate
*/
public void resetConnectionInfoLocked() {
mConnectionInfo.mNumActiveConnections = 0;
mConnectionInfo.mDeviceIndex = 0;
mConnectionInfo.mRetryAttempt = 0;
mConnectionInfo.mDeviceAvailableToConnect = true;
}
public void resetDeviceListLocked() {
if (mDeviceInfoList != null) {
mDeviceInfoList.clear();
mConnectionInfo.mNumPairedDevices = 0;
}
resetConnectionInfoLocked();
}
}