BluetoothControllerImpl.java revision 650639f9a5aeeec9bb697bea50a27bb702205d70
1/*
2 * Copyright (C) 2008 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.systemui.statusbar.policy;
18
19import static android.bluetooth.BluetoothAdapter.ERROR;
20import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
21import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
22import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
23import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
24import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
25import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
26
27import android.bluetooth.BluetoothA2dp;
28import android.bluetooth.BluetoothA2dpSink;
29import android.bluetooth.BluetoothAdapter;
30import android.bluetooth.BluetoothDevice;
31import android.bluetooth.BluetoothHeadset;
32import android.bluetooth.BluetoothHeadsetClient;
33import android.bluetooth.BluetoothInputDevice;
34import android.bluetooth.BluetoothManager;
35import android.bluetooth.BluetoothMap;
36import android.bluetooth.BluetoothPan;
37import android.bluetooth.BluetoothProfile;
38import android.bluetooth.BluetoothProfile.ServiceListener;
39import android.content.BroadcastReceiver;
40import android.content.Context;
41import android.content.Intent;
42import android.content.IntentFilter;
43import android.os.ParcelUuid;
44import android.util.ArrayMap;
45import android.util.ArraySet;
46import android.util.Log;
47import android.util.SparseArray;
48
49import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
50
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.List;
55import java.util.Set;
56
57public class BluetoothControllerImpl implements BluetoothController {
58    private static final String TAG = "BluetoothController";
59    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
60    // This controls the order in which we check the states.  Since a device can only have
61    // one state on screen, but can have multiple profiles, the later states override the
62    // value of earlier states.  So if a device has a profile in CONNECTING and one in
63    // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
64    // but seemed worth noting.
65    private static final int[] CONNECTION_STATES = {
66        BluetoothProfile.STATE_DISCONNECTED,
67        BluetoothProfile.STATE_DISCONNECTING,
68        BluetoothProfile.STATE_CONNECTING,
69        BluetoothProfile.STATE_CONNECTED,
70    };
71
72    private final Context mContext;
73    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
74    private final BluetoothAdapter mAdapter;
75    private final Receiver mReceiver = new Receiver();
76    private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
77    private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
78
79    private boolean mEnabled;
80    private boolean mConnecting;
81    private BluetoothDevice mLastDevice;
82
83    public BluetoothControllerImpl(Context context) {
84        mContext = context;
85        final BluetoothManager bluetoothManager =
86                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
87        mAdapter = bluetoothManager.getAdapter();
88        if (mAdapter == null) {
89            Log.w(TAG, "Default BT adapter not found");
90            return;
91        }
92
93        mReceiver.register();
94        setAdapterState(mAdapter.getState());
95        updateBluetoothDevices();
96        bindAllProfiles();
97    }
98
99    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
100        pw.println("BluetoothController state:");
101        pw.print("  mAdapter="); pw.println(mAdapter);
102        pw.print("  mEnabled="); pw.println(mEnabled);
103        pw.print("  mConnecting="); pw.println(mConnecting);
104        pw.print("  mLastDevice="); pw.println(mLastDevice);
105        pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
106        pw.print("  mProfiles="); pw.println(profilesToString(mProfiles));
107        pw.print("  mDeviceInfo.size="); pw.println(mDeviceInfo.size());
108        for (int i = 0; i < mDeviceInfo.size(); i++) {
109            final BluetoothDevice device = mDeviceInfo.keyAt(i);
110            final DeviceInfo info = mDeviceInfo.valueAt(i);
111            pw.print("    "); pw.print(deviceToString(device));
112            pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
113            pw.print("    "); pw.println(infoToString(info));
114        }
115    }
116
117    private static String infoToString(DeviceInfo info) {
118        return info == null ? null : ("connectionState=" +
119                connectionStateToString(info.connectionState) + ",bonded=" + info.bonded
120                + ",profiles=" + profilesToString(info.connectedProfiles));
121    }
122
123    private static String profilesToString(SparseArray<?> profiles) {
124        final int N = profiles.size();
125        final StringBuffer buffer = new StringBuffer();
126        buffer.append('[');
127        for (int i = 0; i < N; i++) {
128            if (i != 0) {
129                buffer.append(',');
130            }
131            buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
132        }
133        buffer.append(']');
134        return buffer.toString();
135    }
136
137    public void addStateChangedCallback(Callback cb) {
138        mCallbacks.add(cb);
139        fireStateChange(cb);
140    }
141
142    @Override
143    public void removeStateChangedCallback(Callback cb) {
144        mCallbacks.remove(cb);
145    }
146
147    @Override
148    public boolean isBluetoothEnabled() {
149        return mAdapter != null && mAdapter.isEnabled();
150    }
151
152    @Override
153    public boolean isBluetoothConnected() {
154        return mAdapter != null
155                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
156    }
157
158    @Override
159    public boolean isBluetoothConnecting() {
160        return mAdapter != null
161                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
162    }
163
164    @Override
165    public void setBluetoothEnabled(boolean enabled) {
166        if (mAdapter != null) {
167            if (enabled) {
168                mAdapter.enable();
169            } else {
170                mAdapter.disable();
171            }
172        }
173    }
174
175    @Override
176    public boolean isBluetoothSupported() {
177        return mAdapter != null;
178    }
179
180    @Override
181    public ArraySet<PairedDevice> getPairedDevices() {
182        final ArraySet<PairedDevice> rt = new ArraySet<>();
183        for (int i = 0; i < mDeviceInfo.size(); i++) {
184            final BluetoothDevice device = mDeviceInfo.keyAt(i);
185            final DeviceInfo info = mDeviceInfo.valueAt(i);
186            if (!info.bonded) continue;
187            final PairedDevice paired = new PairedDevice();
188            paired.id = device.getAddress();
189            paired.tag = device;
190            paired.name = device.getAliasName();
191            paired.state = connectionStateToPairedDeviceState(info.connectionState);
192            rt.add(paired);
193        }
194        return rt;
195    }
196
197    private static int connectionStateToPairedDeviceState(int state) {
198        if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
199        if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
200        if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
201        return PairedDevice.STATE_DISCONNECTED;
202    }
203
204    @Override
205    public void connect(final PairedDevice pd) {
206        connect(pd, true);
207    }
208
209    @Override
210    public void disconnect(PairedDevice pd) {
211        connect(pd, false);
212    }
213
214    private void connect(PairedDevice pd, final boolean connect) {
215        if (mAdapter == null || pd == null || pd.tag == null) return;
216        final BluetoothDevice device = (BluetoothDevice) pd.tag;
217        final DeviceInfo info = mDeviceInfo.get(device);
218        final String action = connect ? "connect" : "disconnect";
219        if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
220        final ParcelUuid[] uuids = device.getUuids();
221        if (uuids == null) {
222            Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
223            return;
224        }
225        SparseArray<Boolean> profiles = new SparseArray<>();
226        if (connect) {
227            // When connecting add every profile we can recognize by uuid.
228            for (ParcelUuid uuid : uuids) {
229                final int profile = uuidToProfile(uuid);
230                if (profile == 0) {
231                    Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
232                            + uuidToString(uuid));
233                    continue;
234                }
235                final boolean connected = info.connectedProfiles.get(profile, false);
236                if (!connected) {
237                    profiles.put(profile, true);
238                }
239            }
240        } else {
241            // When disconnecting, just add every profile we know they are connected to.
242            profiles = info.connectedProfiles;
243        }
244        for (int i = 0; i < profiles.size(); i++) {
245            final int profile = profiles.keyAt(i);
246            if (mProfiles.indexOfKey(profile) >= 0) {
247                final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
248                final boolean ok = connect ? p.connect(device) : p.disconnect(device);
249                if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
250                        + (ok ? "succeeded" : "failed"));
251            } else {
252                Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
253            }
254        }
255    }
256
257    @Override
258    public String getLastDeviceName() {
259        return mLastDevice != null ? mLastDevice.getAliasName() : null;
260    }
261
262    private void updateBluetoothDevices() {
263        if (mAdapter == null) return;
264        final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
265        for (DeviceInfo info : mDeviceInfo.values()) {
266            info.bonded = false;
267            info.connectionState = ERROR;
268            info.connectedProfiles.clear();
269        }
270        int bondedCount = 0;
271        BluetoothDevice lastBonded = null;
272        if (bondedDevices != null) {
273            for (BluetoothDevice bondedDevice : bondedDevices) {
274                final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
275                updateInfo(bondedDevice).bonded = bonded;
276                if (bonded) {
277                    bondedCount++;
278                    lastBonded = bondedDevice;
279                }
280            }
281        }
282        final int N = mProfiles.size();
283        final int[] connectionType = new int[1];
284        for (int i = 0; i < CONNECTION_STATES.length; i++) {
285            connectionType[0] = CONNECTION_STATES[i];
286            for (int j = 0; j < N; j++) {
287                int profile = mProfiles.keyAt(j);
288                List<BluetoothDevice> devices = mProfiles.get(profile)
289                        .getDevicesMatchingConnectionStates(connectionType);
290                for (int k = 0; k < devices.size(); k++) {
291                    DeviceInfo info = mDeviceInfo.get(devices.get(k));
292                    if (info != null) {
293                        info.connectionState = CONNECTION_STATES[i];
294                        if (CONNECTION_STATES[i] == BluetoothProfile.STATE_CONNECTED) {
295                            info.connectedProfiles.put(profile, true);
296                        }
297                    }
298                }
299            }
300        }
301        if (mLastDevice == null && bondedCount == 1) {
302            mLastDevice = lastBonded;
303        }
304        // If we are no longer connected to the current device, see if we are connected to
305        // something else, so we don't display a name we aren't connected to.
306        if (mLastDevice != null &&
307                mDeviceInfo.get(mLastDevice).connectionState != BluetoothProfile.STATE_CONNECTED) {
308            // Make sure we don't keep this device while it isn't connected.
309            mLastDevice = null;
310            // Look for anything else connected.
311            final int size = mDeviceInfo.size();
312            for (int i = 0; i < size; i++) {
313                BluetoothDevice device = mDeviceInfo.keyAt(i);
314                DeviceInfo info = mDeviceInfo.valueAt(i);
315                if (info.connectionState == BluetoothProfile.STATE_CONNECTED) {
316                    mLastDevice = device;
317                    break;
318                }
319            }
320        }
321        firePairedDevicesChanged();
322    }
323
324    private void bindAllProfiles() {
325        // Note: This needs to contain all of the types that can be returned by BluetoothUtil
326        // otherwise we can't find the profiles we need when we connect/disconnect.
327        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
328        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
329        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
330        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
331        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
332        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
333        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
334        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
335        // Note Health is not in this list because health devices aren't 'connected'.
336        // If profiles are expanded to use more than just connection state and connect/disconnect
337        // then it should be added.
338    }
339
340    private void firePairedDevicesChanged() {
341        for (Callback cb : mCallbacks) {
342            cb.onBluetoothPairedDevicesChanged();
343        }
344    }
345
346    private void setAdapterState(int adapterState) {
347        final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
348        if (mEnabled == enabled) return;
349        mEnabled = enabled;
350        fireStateChange();
351    }
352
353    private void setConnecting(boolean connecting) {
354        if (mConnecting == connecting) return;
355        mConnecting = connecting;
356        fireStateChange();
357    }
358
359    private void fireStateChange() {
360        for (Callback cb : mCallbacks) {
361            fireStateChange(cb);
362        }
363    }
364
365    private void fireStateChange(Callback cb) {
366        cb.onBluetoothStateChange(mEnabled, mConnecting);
367    }
368
369    private final ServiceListener mProfileListener = new ServiceListener() {
370        @Override
371        public void onServiceDisconnected(int profile) {
372            mProfiles.remove(profile);
373            updateBluetoothDevices();
374        }
375
376        @Override
377        public void onServiceConnected(int profile, BluetoothProfile proxy) {
378            mProfiles.put(profile, proxy);
379            updateBluetoothDevices();
380        }
381    };
382
383    private final class Receiver extends BroadcastReceiver {
384        public void register() {
385            final IntentFilter filter = new IntentFilter();
386            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
387            filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
388            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
389            filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
390            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
391            filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
392            filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
393            filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
394            filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
395            filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
396            filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
397            mContext.registerReceiver(this, filter);
398        }
399
400        @Override
401        public void onReceive(Context context, Intent intent) {
402            final String action = intent.getAction();
403            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
404            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
405                setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
406                if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
407            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
408                updateInfo(device);
409                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
410                        ERROR);
411                mLastDevice = device;
412                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
413                        + connectionStateToString(state) + " " + deviceToString(device));
414                setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
415            } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
416                updateInfo(device);
417                mLastDevice = device;
418            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
419                if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
420                // we'll update all bonded devices below
421            }
422            // Always update bluetooth devices state.
423            updateBluetoothDevices();
424        }
425    }
426
427    private DeviceInfo updateInfo(BluetoothDevice device) {
428        DeviceInfo info = mDeviceInfo.get(device);
429        info = info != null ? info : new DeviceInfo();
430        mDeviceInfo.put(device, info);
431        return info;
432    }
433
434    private static class DeviceInfo {
435        int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
436        boolean bonded;  // per getBondedDevices
437        SparseArray<Boolean> connectedProfiles = new SparseArray<>();
438    }
439}
440