BluetoothControllerImpl: track bluetooth better
Match settings behavior better:
- Get connection info from profiles so it is for all devices
(CONNECTION_STATE_CHANGED only updates one)
- Disconnect from all known connections so that 'x' does something
- When device is disconnected without CONNECTION_STATE_CHANGED
then find a new 'last' device that is connected
- Listen to a bunch more broadcasts to make sure info is always
up to date.
Bug: 18210666
Bug: 18638010
Change-Id: Ie003b67bbdd0d452b7472c644d90bca929e5cd29
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 80fec5b..076cfe2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -19,15 +19,21 @@
import static android.bluetooth.BluetoothAdapter.ERROR;
import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString;
import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
@@ -38,24 +44,37 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.SparseBooleanArray;
+import android.util.SparseArray;
import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
public class BluetoothControllerImpl implements BluetoothController {
private static final String TAG = "BluetoothController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // This controls the order in which we check the states. Since a device can only have
+ // one state on screen, but can have multiple profiles, the later states override the
+ // value of earlier states. So if a device has a profile in CONNECTING and one in
+ // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
+ // but seemed worth noting.
+ private static final int[] CONNECTION_STATES = {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_CONNECTED,
+ };
private final Context mContext;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final BluetoothAdapter mAdapter;
private final Receiver mReceiver = new Receiver();
private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
+ private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
private boolean mEnabled;
private boolean mConnecting;
@@ -73,7 +92,8 @@
mReceiver.register();
setAdapterState(mAdapter.getState());
- updateBondedBluetoothDevices();
+ updateBluetoothDevices();
+ bindAllProfiles();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -83,6 +103,7 @@
pw.print(" mConnecting="); pw.println(mConnecting);
pw.print(" mLastDevice="); pw.println(mLastDevice);
pw.print(" mCallbacks.size="); pw.println(mCallbacks.size());
+ pw.print(" mProfiles="); pw.println(profilesToString(mProfiles));
pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size());
for (int i = 0; i < mDeviceInfo.size(); i++) {
final BluetoothDevice device = mDeviceInfo.keyAt(i);
@@ -95,7 +116,22 @@
private static String infoToString(DeviceInfo info) {
return info == null ? null : ("connectionState=" +
- connectionStateToString(info.connectionState) + ",bonded=" + info.bonded);
+ connectionStateToString(info.connectionState) + ",bonded=" + info.bonded
+ + ",profiles=" + profilesToString(info.connectedProfiles));
+ }
+
+ private static String profilesToString(SparseArray<?> profiles) {
+ final int N = profiles.size();
+ final StringBuffer buffer = new StringBuffer();
+ buffer.append('[');
+ for (int i = 0; i < N; i++) {
+ if (i != 0) {
+ buffer.append(',');
+ }
+ buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
+ }
+ buffer.append(']');
+ return buffer.toString();
}
public void addStateChangedCallback(Callback cb) {
@@ -178,6 +214,7 @@
private void connect(PairedDevice pd, final boolean connect) {
if (mAdapter == null || pd == null || pd.tag == null) return;
final BluetoothDevice device = (BluetoothDevice) pd.tag;
+ final DeviceInfo info = mDeviceInfo.get(device);
final String action = connect ? "connect" : "disconnect";
if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
final ParcelUuid[] uuids = device.getUuids();
@@ -185,43 +222,35 @@
Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
return;
}
- final SparseBooleanArray profiles = new SparseBooleanArray();
- for (ParcelUuid uuid : uuids) {
- final int profile = uuidToProfile(uuid);
- if (profile == 0) {
- Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
- + uuidToString(uuid));
- continue;
+ SparseArray<Boolean> profiles = new SparseArray<>();
+ if (connect) {
+ // When connecting add every profile we can recognize by uuid.
+ for (ParcelUuid uuid : uuids) {
+ final int profile = uuidToProfile(uuid);
+ if (profile == 0) {
+ Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
+ + uuidToString(uuid));
+ continue;
+ }
+ final boolean connected = info.connectedProfiles.get(profile, false);
+ if (!connected) {
+ profiles.put(profile, true);
+ }
}
- final int profileState = mAdapter.getProfileConnectionState(profile);
- if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
- + " state = " + profileStateToString(profileState));
- final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
- if (connect != connected) {
- profiles.put(profile, true);
- }
+ } else {
+ // When disconnecting, just add every profile we know they are connected to.
+ profiles = info.connectedProfiles;
}
for (int i = 0; i < profiles.size(); i++) {
final int profile = profiles.keyAt(i);
- mAdapter.getProfileProxy(mContext, new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
- final Profile p = BluetoothUtil.getProfile(proxy);
- if (p == null) {
- Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
- } else {
- final boolean ok = connect ? p.connect(device) : p.disconnect(device);
- if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
- + (ok ? "succeeded" : "failed"));
- }
- }
-
- @Override
- public void onServiceDisconnected(int profile) {
- if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
- }
- }, profile);
+ if (mProfiles.indexOfKey(profile) >= 0) {
+ final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
+ final boolean ok = connect ? p.connect(device) : p.disconnect(device);
+ if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
+ + (ok ? "succeeded" : "failed"));
+ } else {
+ Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
+ }
}
}
@@ -230,11 +259,13 @@
return mLastDevice != null ? mLastDevice.getAliasName() : null;
}
- private void updateBondedBluetoothDevices() {
+ private void updateBluetoothDevices() {
if (mAdapter == null) return;
final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
for (DeviceInfo info : mDeviceInfo.values()) {
info.bonded = false;
+ info.connectionState = ERROR;
+ info.connectedProfiles.clear();
}
int bondedCount = 0;
BluetoothDevice lastBonded = null;
@@ -248,12 +279,62 @@
}
}
}
+ final int N = mProfiles.size();
+ final int[] connectionType = new int[1];
+ for (int i = 0; i < CONNECTION_STATES.length; i++) {
+ connectionType[0] = CONNECTION_STATES[i];
+ for (int j = 0; j < N; j++) {
+ int profile = mProfiles.keyAt(j);
+ List<BluetoothDevice> devices = mProfiles.get(profile)
+ .getDevicesMatchingConnectionStates(connectionType);
+ for (int k = 0; k < devices.size(); k++) {
+ DeviceInfo info = mDeviceInfo.get(devices.get(k));
+ info.connectionState = CONNECTION_STATES[i];
+ if (CONNECTION_STATES[i] == BluetoothProfile.STATE_CONNECTED) {
+ info.connectedProfiles.put(profile, true);
+ }
+ }
+ }
+ }
if (mLastDevice == null && bondedCount == 1) {
mLastDevice = lastBonded;
}
+ // If we are no longer connected to the current device, see if we are connected to
+ // something else, so we don't display a name we aren't connected to.
+ if (mLastDevice != null &&
+ mDeviceInfo.get(mLastDevice).connectionState != BluetoothProfile.STATE_CONNECTED) {
+ // Make sure we don't keep this device while it isn't connected.
+ mLastDevice = null;
+ // Look for anything else connected.
+ final int size = mDeviceInfo.size();
+ for (int i = 0; i < size; i++) {
+ BluetoothDevice device = mDeviceInfo.keyAt(i);
+ DeviceInfo info = mDeviceInfo.valueAt(i);
+ if (info.connectionState == BluetoothProfile.STATE_CONNECTED) {
+ mLastDevice = device;
+ break;
+ }
+ }
+ }
firePairedDevicesChanged();
}
+ private void bindAllProfiles() {
+ // Note: This needs to contain all of the types that can be returned by BluetoothUtil
+ // otherwise we can't find the profiles we need when we connect/disconnect.
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
+ mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
+ // Note Health is not in this list because health devices aren't 'connected'.
+ // If profiles are expanded to use more than just connection state and connect/disconnect
+ // then it should be added.
+ }
+
private void firePairedDevicesChanged() {
for (Callback cb : mCallbacks) {
cb.onBluetoothPairedDevicesChanged();
@@ -283,6 +364,20 @@
cb.onBluetoothStateChange(mEnabled, mConnecting);
}
+ private final ServiceListener mProfileListener = new ServiceListener() {
+ @Override
+ public void onServiceDisconnected(int profile) {
+ mProfiles.remove(profile);
+ updateBluetoothDevices();
+ }
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mProfiles.put(profile, proxy);
+ updateBluetoothDevices();
+ }
+ };
+
private final class Receiver extends BroadcastReceiver {
public void register() {
final IntentFilter filter = new IntentFilter();
@@ -290,6 +385,15 @@
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_UUID);
+ filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
mContext.registerReceiver(this, filter);
}
@@ -301,16 +405,13 @@
setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
} else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- final DeviceInfo info = updateInfo(device);
+ updateInfo(device);
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
ERROR);
- if (state != ERROR) {
- info.connectionState = state;
- }
mLastDevice = device;
if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
+ connectionStateToString(state) + " " + deviceToString(device));
- setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
+ setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
} else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
updateInfo(device);
mLastDevice = device;
@@ -318,7 +419,8 @@
if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
// we'll update all bonded devices below
}
- updateBondedBluetoothDevices();
+ // Always update bluetooth devices state.
+ updateBluetoothDevices();
}
}
@@ -332,5 +434,6 @@
private static class DeviceInfo {
int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
boolean bonded; // per getBondedDevices
+ SparseArray<Boolean> connectedProfiles = new SparseArray<>();
}
}