Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4630c89a authored by Jason Monk's avatar Jason Monk
Browse files

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
parent c5c93edd
Loading
Loading
Loading
Loading
+147 −44
Original line number Diff line number Diff line
@@ -19,15 +19,21 @@ package com.android.systemui.statusbar.policy;
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.os.ParcelUuid;
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 @@ public class BluetoothControllerImpl implements BluetoothController {

        mReceiver.register();
        setAdapterState(mAdapter.getState());
        updateBondedBluetoothDevices();
        updateBluetoothDevices();
        bindAllProfiles();
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -83,6 +103,7 @@ public class BluetoothControllerImpl implements BluetoothController {
        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 @@ public class BluetoothControllerImpl implements BluetoothController {

    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 @@ public class BluetoothControllerImpl implements BluetoothController {
    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,7 +222,9 @@ public class BluetoothControllerImpl implements BluetoothController {
            Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
            return;
        }
        final SparseBooleanArray profiles = new SparseBooleanArray();
        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) {
@@ -193,36 +232,26 @@ public class BluetoothControllerImpl implements BluetoothController {
                            + uuidToString(uuid));
                    continue;
                }
            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) {
                final boolean connected = info.connectedProfiles.get(profile, false);
                if (!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 {
            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));
            }
        }

                @Override
                public void onServiceDisconnected(int profile) {
                    if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
                }
            }, profile);
        }
    }

    @Override
@@ -230,11 +259,13 @@ public class BluetoothControllerImpl implements BluetoothController {
        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 @@ public class BluetoothControllerImpl implements BluetoothController {
                }
            }
        }
        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 @@ public class BluetoothControllerImpl implements BluetoothController {
        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 @@ public class BluetoothControllerImpl implements BluetoothController {
            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 @@ public class BluetoothControllerImpl implements BluetoothController {
                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 @@ public class BluetoothControllerImpl implements BluetoothController {
                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 @@ public class BluetoothControllerImpl implements BluetoothController {
    private static class DeviceInfo {
        int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
        boolean bonded;  // per getBondedDevices
        SparseArray<Boolean> connectedProfiles = new SparseArray<>();
    }
}
+9 −1
Original line number Diff line number Diff line
@@ -36,7 +36,10 @@ public class BluetoothUtil {
        if (profile == BluetoothProfile.HEADSET) return "HEADSET";
        if (profile == BluetoothProfile.A2DP) return "A2DP";
        if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
        return "UNKNOWN";
        if (profile == BluetoothProfile.PAN) return "PAN";
        if (profile == BluetoothProfile.INPUT_DEVICE) return "INPUT_DEVICE";
        if (profile == BluetoothProfile.MAP) return "MAP";
        return "UNKNOWN(" + profile + ")";
    }

    public static String profileStateToString(int state) {
@@ -106,6 +109,11 @@ public class BluetoothUtil {

        if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;

        if (BluetoothUuid.Hid.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
        if (BluetoothUuid.Hogp.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;

        if (BluetoothUuid.NAP.equals(uuid)) return BluetoothProfile.PAN;

        return 0;
    }