1af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock/*
2af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * Copyright (C) 2008 The Android Open Source Project
3af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *
4af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * Licensed under the Apache License, Version 2.0 (the "License");
5af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * you may not use this file except in compliance with the License.
6af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * You may obtain a copy of the License at
7af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *
8af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *      http://www.apache.org/licenses/LICENSE-2.0
9af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *
10af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * Unless required by applicable law or agreed to in writing, software
11af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * distributed under the License is distributed on an "AS IS" BASIS,
12af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * See the License for the specific language governing permissions and
14af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * limitations under the License.
15af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock */
16af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
17af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockpackage com.android.systemui.statusbar.policy;
18af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
19486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static android.bluetooth.BluetoothAdapter.ERROR;
20486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
21486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
22486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
23486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
24486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
25486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
26486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
274630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothA2dp;
284630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothA2dpSink;
29af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.bluetooth.BluetoothAdapter;
30af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.bluetooth.BluetoothDevice;
314630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothHeadset;
324630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothHeadsetClient;
334630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothInputDevice;
34486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.bluetooth.BluetoothManager;
354630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothMap;
364630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.bluetooth.BluetoothPan;
37486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.bluetooth.BluetoothProfile;
38486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.bluetooth.BluetoothProfile.ServiceListener;
39af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.content.BroadcastReceiver;
40af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.content.Context;
41af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.content.Intent;
42af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.content.IntentFilter;
434ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monkimport android.os.Handler;
444ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monkimport android.os.Looper;
454ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monkimport android.os.Message;
46486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.os.ParcelUuid;
47486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.util.ArrayMap;
48486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.util.ArraySet;
49486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport android.util.Log;
504630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monkimport android.util.SparseArray;
51486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
52486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
53af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
54486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport java.io.FileDescriptor;
55486b78e42652466f6241eb87d5bed60040db7a25John Spurlockimport java.io.PrintWriter;
56af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport java.util.ArrayList;
57af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport java.util.Set;
58af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
59486b78e42652466f6241eb87d5bed60040db7a25John Spurlockpublic class BluetoothControllerImpl implements BluetoothController {
60486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private static final String TAG = "BluetoothController";
61486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
624630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    // This controls the order in which we check the states.  Since a device can only have
634630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    // one state on screen, but can have multiple profiles, the later states override the
644630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    // value of earlier states.  So if a device has a profile in CONNECTING and one in
654630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
664630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    // but seemed worth noting.
674630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    private static final int[] CONNECTION_STATES = {
684630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        BluetoothProfile.STATE_DISCONNECTED,
694630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        BluetoothProfile.STATE_DISCONNECTING,
704630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        BluetoothProfile.STATE_CONNECTING,
714630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        BluetoothProfile.STATE_CONNECTED,
724630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    };
734ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    // Update all the BT device states.
744ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static final int MSG_UPDATE_CONNECTION_STATES = 1;
754ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    // Update just one BT device.
764ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
774ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    // Update whether devices are bonded or not.
784ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static final int MSG_UPDATE_BONDED_DEVICES = 3;
794ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
804ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static final int MSG_ADD_PROFILE = 4;
814ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static final int MSG_REM_PROFILE = 5;
82af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
83486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private final Context mContext;
84d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
85af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    private final BluetoothAdapter mAdapter;
86486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private final Receiver mReceiver = new Receiver();
87486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
884630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
89af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
904ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private final H mHandler;
914ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
92d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    private boolean mEnabled;
93d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    private boolean mConnecting;
94d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    private BluetoothDevice mLastDevice;
95af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
964ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    public BluetoothControllerImpl(Context context, Looper bgLooper) {
97486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        mContext = context;
984ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler = new H(bgLooper);
994ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
100486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        final BluetoothManager bluetoothManager =
101486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
102486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        mAdapter = bluetoothManager.getAdapter();
103486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (mAdapter == null) {
104486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            Log.w(TAG, "Default BT adapter not found");
105486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            return;
106af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
107486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
108486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        mReceiver.register();
109486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        setAdapterState(mAdapter.getState());
1104ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        updateBondedDevices();
1114630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        bindAllProfiles();
112af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
113af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
114486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
115486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.println("BluetoothController state:");
116486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.print("  mAdapter="); pw.println(mAdapter);
117486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.print("  mEnabled="); pw.println(mEnabled);
118486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.print("  mConnecting="); pw.println(mConnecting);
119486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.print("  mLastDevice="); pw.println(mLastDevice);
120486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
1214630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        pw.print("  mProfiles="); pw.println(profilesToString(mProfiles));
122486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        pw.print("  mDeviceInfo.size="); pw.println(mDeviceInfo.size());
123486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        for (int i = 0; i < mDeviceInfo.size(); i++) {
124486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final BluetoothDevice device = mDeviceInfo.keyAt(i);
125486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final DeviceInfo info = mDeviceInfo.valueAt(i);
126486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            pw.print("    "); pw.print(deviceToString(device));
127486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
128486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            pw.print("    "); pw.println(infoToString(info));
129486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
130486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
131486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
132486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private static String infoToString(DeviceInfo info) {
133486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        return info == null ? null : ("connectionState=" +
1344ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
1354ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                + ",bonded=" + info.bonded + ",profiles="
1364ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                + profilesToString(info.connectedProfiles));
1374630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    }
1384630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk
1394630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    private static String profilesToString(SparseArray<?> profiles) {
1404630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        final int N = profiles.size();
1414630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        final StringBuffer buffer = new StringBuffer();
1424630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        buffer.append('[');
1434630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        for (int i = 0; i < N; i++) {
1444630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            if (i != 0) {
1454630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                buffer.append(',');
1464630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            }
1474630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
1484630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        }
1494630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        buffer.append(']');
1504630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        return buffer.toString();
151486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
152486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
153d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    public void addStateChangedCallback(Callback cb) {
154d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock        mCallbacks.add(cb);
155486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        fireStateChange(cb);
156af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
157af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
158af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
159d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    public void removeStateChangedCallback(Callback cb) {
160d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock        mCallbacks.remove(cb);
161af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
162af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
163af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
164af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public boolean isBluetoothEnabled() {
165af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        return mAdapter != null && mAdapter.isEnabled();
166af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
167af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
168af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
169af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public boolean isBluetoothConnected() {
170af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        return mAdapter != null
171af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
172af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
173af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
174af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
175d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    public boolean isBluetoothConnecting() {
176d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock        return mAdapter != null
177d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
178d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    }
179d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock
180d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    @Override
181af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public void setBluetoothEnabled(boolean enabled) {
182af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        if (mAdapter != null) {
183af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            if (enabled) {
184af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock                mAdapter.enable();
185af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            } else {
186af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock                mAdapter.disable();
187af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            }
188af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
189af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
190af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
191af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
192af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public boolean isBluetoothSupported() {
193af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        return mAdapter != null;
194af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
195af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
196486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    @Override
197486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    public ArraySet<PairedDevice> getPairedDevices() {
198486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        final ArraySet<PairedDevice> rt = new ArraySet<>();
199486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        for (int i = 0; i < mDeviceInfo.size(); i++) {
200486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final BluetoothDevice device = mDeviceInfo.keyAt(i);
201486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final DeviceInfo info = mDeviceInfo.valueAt(i);
202486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            if (!info.bonded) continue;
203486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final PairedDevice paired = new PairedDevice();
204486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            paired.id = device.getAddress();
205486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            paired.tag = device;
206486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            paired.name = device.getAliasName();
2074ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
208486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            rt.add(paired);
209486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
210486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        return rt;
211486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
212486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
2134ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static int connectionStateToPairedDeviceState(int index) {
2144ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        int state = CONNECTION_STATES[index];
215486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
216486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
217486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
218486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        return PairedDevice.STATE_DISCONNECTED;
219af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
220af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
221af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
222486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    public void connect(final PairedDevice pd) {
223486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        connect(pd, true);
224d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    }
225d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock
226d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock    @Override
227486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    public void disconnect(PairedDevice pd) {
228486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        connect(pd, false);
229486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
230af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
231486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private void connect(PairedDevice pd, final boolean connect) {
232486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (mAdapter == null || pd == null || pd.tag == null) return;
233486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        final BluetoothDevice device = (BluetoothDevice) pd.tag;
2344630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        final DeviceInfo info = mDeviceInfo.get(device);
235486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        final String action = connect ? "connect" : "disconnect";
236486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
23727bdff7750f7f85fd8922385707342d37e59e406John Spurlock        final ParcelUuid[] uuids = device.getUuids();
23827bdff7750f7f85fd8922385707342d37e59e406John Spurlock        if (uuids == null) {
23927bdff7750f7f85fd8922385707342d37e59e406John Spurlock            Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
24027bdff7750f7f85fd8922385707342d37e59e406John Spurlock            return;
24127bdff7750f7f85fd8922385707342d37e59e406John Spurlock        }
2424630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        SparseArray<Boolean> profiles = new SparseArray<>();
2434630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        if (connect) {
2444630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            // When connecting add every profile we can recognize by uuid.
2454630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            for (ParcelUuid uuid : uuids) {
2464630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                final int profile = uuidToProfile(uuid);
2474630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                if (profile == 0) {
2484630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                    Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
2494630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                            + uuidToString(uuid));
2504630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                    continue;
2514630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                }
2524630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                final boolean connected = info.connectedProfiles.get(profile, false);
2534630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                if (!connected) {
2544630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                    profiles.put(profile, true);
2554630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                }
256486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            }
2574630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        } else {
2584630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            // When disconnecting, just add every profile we know they are connected to.
2594630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            profiles = info.connectedProfiles;
260a625b7412069342c30e9f964ce9abc969bf097b4John Spurlock        }
261486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        for (int i = 0; i < profiles.size(); i++) {
262486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final int profile = profiles.keyAt(i);
2634630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            if (mProfiles.indexOfKey(profile) >= 0) {
2644630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
2654630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                final boolean ok = connect ? p.connect(device) : p.disconnect(device);
2664630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
2674630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                        + (ok ? "succeeded" : "failed"));
2684630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            } else {
2694630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
2704630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            }
271d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock        }
272486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
273486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
274486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    @Override
275486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    public String getLastDeviceName() {
276486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        return mLastDevice != null ? mLastDevice.getAliasName() : null;
277af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
278af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
2794ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void updateBondedDevices() {
2804ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
2814ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
2824ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
2834ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
2844ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void updateConnectionStates() {
2854ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
2864ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
2874ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
2884ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
2894ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
2904ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void updateConnectionState(BluetoothDevice device, int profile, int state) {
2914ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) {
2924ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            // If we are about to update all the devices, then we don't need to update this one.
2934ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return;
2944ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        }
2954ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device)
2964ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                .sendToTarget();
2974ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
2984ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
2994ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void handleUpdateBondedDevices() {
300486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (mAdapter == null) return;
301486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
302486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        for (DeviceInfo info : mDeviceInfo.values()) {
303486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            info.bonded = false;
304486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
305486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        int bondedCount = 0;
306486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        BluetoothDevice lastBonded = null;
307486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (bondedDevices != null) {
308486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            for (BluetoothDevice bondedDevice : bondedDevices) {
309486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
310486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                updateInfo(bondedDevice).bonded = bonded;
311486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                if (bonded) {
312486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                    bondedCount++;
313486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                    lastBonded = bondedDevice;
314af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock                }
315af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            }
316af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
3174ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        if (mLastDevice == null && bondedCount == 1) {
3184ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            mLastDevice = lastBonded;
3194ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        }
3204ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        updateConnectionStates();
3214ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        firePairedDevicesChanged();
3224ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
3234ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
3244ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void handleUpdateConnectionStates() {
3254ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        final int N = mDeviceInfo.size();
3264ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        for (int i = 0; i < N; i++) {
3274ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            BluetoothDevice device = mDeviceInfo.keyAt(i);
3284ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            DeviceInfo info = updateInfo(device);
3294ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            info.connectionStateIndex = 0;
3304ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            info.connectedProfiles.clear();
3314ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            for (int j = 0; j < mProfiles.size(); j++) {
3324ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                int state = mProfiles.valueAt(j).getConnectionState(device);
3334ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                handleUpdateConnectionState(device, mProfiles.keyAt(j), state);
3344ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            }
3354ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        }
3364ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        handleConnectionChange();
3374ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        firePairedDevicesChanged();
3384ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
3394ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
3404ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) {
3414ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device)
3424ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                + " " + BluetoothUtil.profileToString(profile)
3434ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                + " " + BluetoothUtil.connectionStateToString(state));
3444ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        DeviceInfo info = updateInfo(device);
3454ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        int stateIndex = 0;
3464630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        for (int i = 0; i < CONNECTION_STATES.length; i++) {
3474ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            if (CONNECTION_STATES[i] == state) {
3484ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                stateIndex = i;
3494ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                break;
3504630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            }
3514630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        }
3524ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        info.profileStates.put(profile, stateIndex);
3534ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
3544ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        info.connectionStateIndex = 0;
3554ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        final int N = info.profileStates.size();
3564ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        for (int i = 0; i < N; i++) {
3574ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            if (info.profileStates.valueAt(i) > info.connectionStateIndex) {
3584ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                info.connectionStateIndex = info.profileStates.valueAt(i);
3594ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            }
3604ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        }
3614ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        if (state == BluetoothProfile.STATE_CONNECTED) {
3624ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            info.connectedProfiles.put(profile, true);
3634ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else {
3644ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            info.connectedProfiles.remove(profile);
365486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
3664ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
3674ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
3684ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private void handleConnectionChange() {
3694630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // If we are no longer connected to the current device, see if we are connected to
3704630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // something else, so we don't display a name we aren't connected to.
3714630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        if (mLastDevice != null &&
3724ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex]
3734ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                        != BluetoothProfile.STATE_CONNECTED) {
3744630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            // Make sure we don't keep this device while it isn't connected.
3754630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            mLastDevice = null;
3764630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            // Look for anything else connected.
3774630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            final int size = mDeviceInfo.size();
3784630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            for (int i = 0; i < size; i++) {
3794630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                BluetoothDevice device = mDeviceInfo.keyAt(i);
3804630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                DeviceInfo info = mDeviceInfo.valueAt(i);
3814ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                if (CONNECTION_STATES[info.connectionStateIndex]
3824ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                        == BluetoothProfile.STATE_CONNECTED) {
3834630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                    mLastDevice = device;
3844630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                    break;
3854630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                }
3864630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            }
3874630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        }
388486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
389486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
3904630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    private void bindAllProfiles() {
3914630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // Note: This needs to contain all of the types that can be returned by BluetoothUtil
3924630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // otherwise we can't find the profiles we need when we connect/disconnect.
3934630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
3944630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
3954630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
3964630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
3974630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
3984630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
3994630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
4004630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
4014630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // Note Health is not in this list because health devices aren't 'connected'.
4024630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // If profiles are expanded to use more than just connection state and connect/disconnect
4034630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        // then it should be added.
4044630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    }
4054630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk
406486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private void firePairedDevicesChanged() {
407486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        for (Callback cb : mCallbacks) {
408486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            cb.onBluetoothPairedDevicesChanged();
409486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
410486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
411486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
412486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private void setAdapterState(int adapterState) {
413486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
414486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (mEnabled == enabled) return;
415486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        mEnabled = enabled;
416486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        fireStateChange();
417af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
418af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
419486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private void setConnecting(boolean connecting) {
420486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        if (mConnecting == connecting) return;
421486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        mConnecting = connecting;
422486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        fireStateChange();
423af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
424af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
425486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private void fireStateChange() {
426d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock        for (Callback cb : mCallbacks) {
427486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            fireStateChange(cb);
428af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
429af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
430ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock
431486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private void fireStateChange(Callback cb) {
432d1c86e2cb272f8b8be5b9b47aa4ec7084fe61c22John Spurlock        cb.onBluetoothStateChange(mEnabled, mConnecting);
433ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock    }
434486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
4354ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private static int getProfileFromAction(String action) {
4364ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4374ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.A2DP;
4384ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4394ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.HEADSET;
4404ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4414ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.A2DP_SINK;
4424ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4434ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.HEADSET_CLIENT;
4444ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4454ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.INPUT_DEVICE;
4464ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4474ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.MAP;
4484ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
4494ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            return BluetoothProfile.PAN;
4504ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        }
4514ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        if (DEBUG) Log.d(TAG, "Unknown action " + action);
4524ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        return -1;
4534ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    }
4544ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
4554630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    private final ServiceListener mProfileListener = new ServiceListener() {
4564630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        @Override
4574630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        public void onServiceDisconnected(int profile) {
4584ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile));
4594ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            // We lost a profile, don't do any updates until it gets removed.
4604ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
4614ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
4624ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget();
4634630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        }
4644630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk
4654630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        @Override
4664630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        public void onServiceConnected(int profile, BluetoothProfile proxy) {
4674ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile));
4684ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget();
4694630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        }
4704630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk    };
4714630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk
472486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private final class Receiver extends BroadcastReceiver {
473486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        public void register() {
474486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final IntentFilter filter = new IntentFilter();
475486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
476486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
477486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
478486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
4794630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
4804630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
4814630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
4824630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
4834630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
4844630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
4854630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk            filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
486486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            mContext.registerReceiver(this, filter);
487486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
488486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
489486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        @Override
490486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        public void onReceive(Context context, Intent intent) {
491486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final String action = intent.getAction();
492486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
4934ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
494486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
495486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
4964ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                updateBondedDevices();
497486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
498486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
4994630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                updateInfo(device);
500486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
501486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                        ERROR);
502486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                mLastDevice = device;
503486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
504486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                        + connectionStateToString(state) + " " + deviceToString(device));
5054630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk                setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
506486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
507486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                updateInfo(device);
508486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                mLastDevice = device;
509486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
510486b78e42652466f6241eb87d5bed60040db7a25John Spurlock                if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
5114ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                updateBondedDevices();
5124ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            } else {
5134ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                int profile = getProfileFromAction(intent.getAction());
5144ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
5154ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
5164ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                        + BluetoothUtil.profileToString(profile)
5174ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                        + " " + BluetoothUtil.connectionStateToString(state));
5184ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                if ((profile != -1) && (state != -1)) {
5194ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    updateConnectionState(device, profile, state);
5204ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                }
521486b78e42652466f6241eb87d5bed60040db7a25John Spurlock            }
522486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        }
523486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
524486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
525486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private DeviceInfo updateInfo(BluetoothDevice device) {
526486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        DeviceInfo info = mDeviceInfo.get(device);
527486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        info = info != null ? info : new DeviceInfo();
528486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        mDeviceInfo.put(device, info);
529486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        return info;
530486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
531486b78e42652466f6241eb87d5bed60040db7a25John Spurlock
5324ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    private class H extends Handler {
5334ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        public H(Looper l) {
5344ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            super(l);
5354ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        }
5364ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
5374ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        public void handleMessage(Message msg) {
5384ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            switch (msg.what) {
5394ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                case MSG_UPDATE_CONNECTION_STATES:
5404ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    handleUpdateConnectionStates();
5414ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    firePairedDevicesChanged();
5424ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    break;
5434ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                case MSG_UPDATE_SINGLE_CONNECTION_STATE:
5444ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
5454ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    handleConnectionChange();
5464ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    firePairedDevicesChanged();
5474ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    break;
5484ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                case MSG_UPDATE_BONDED_DEVICES:
5494ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    handleUpdateBondedDevices();
5504ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    firePairedDevicesChanged();
5514ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    break;
5524ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                case MSG_ADD_PROFILE:
5534ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj);
5544ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    handleUpdateConnectionStates();
5554ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    firePairedDevicesChanged();
5564ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    break;
5574ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                case MSG_REM_PROFILE:
5584ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    mProfiles.remove(msg.arg1);
5594ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    handleUpdateConnectionStates();
5604ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    firePairedDevicesChanged();
5614ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk                    break;
5624ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk            }
5634ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        };
5644ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk    };
5654ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk
566486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    private static class DeviceInfo {
5674ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        int connectionStateIndex = 0;
568486b78e42652466f6241eb87d5bed60040db7a25John Spurlock        boolean bonded;  // per getBondedDevices
5694630c89a7642d1ae0d528f6d8e23cf89cba01b7dJason Monk        SparseArray<Boolean> connectedProfiles = new SparseArray<>();
5704ae97d3632edf4fd00fe23b9a2304e4e0f2348b7Jason Monk        SparseArray<Integer> profileStates = new SparseArray<>();
571486b78e42652466f6241eb87d5bed60040db7a25John Spurlock    }
572af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock}
573