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.profileStateToString;
23import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
24import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
25import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
26import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
27
28import android.bluetooth.BluetoothAdapter;
29import android.bluetooth.BluetoothDevice;
30import android.bluetooth.BluetoothManager;
31import android.bluetooth.BluetoothProfile;
32import android.bluetooth.BluetoothProfile.ServiceListener;
33import android.content.BroadcastReceiver;
34import android.content.Context;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.os.ParcelUuid;
38import android.util.ArrayMap;
39import android.util.ArraySet;
40import android.util.Log;
41import android.util.SparseBooleanArray;
42
43import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
44
45import java.io.FileDescriptor;
46import java.io.PrintWriter;
47import java.util.ArrayList;
48import java.util.Set;
49
50public class BluetoothControllerImpl implements BluetoothController {
51    private static final String TAG = "BluetoothController";
52    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
53
54    private final Context mContext;
55    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
56    private final BluetoothAdapter mAdapter;
57    private final Receiver mReceiver = new Receiver();
58    private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
59
60    private boolean mEnabled;
61    private boolean mConnecting;
62    private BluetoothDevice mLastDevice;
63
64    public BluetoothControllerImpl(Context context) {
65        mContext = context;
66        final BluetoothManager bluetoothManager =
67                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
68        mAdapter = bluetoothManager.getAdapter();
69        if (mAdapter == null) {
70            Log.w(TAG, "Default BT adapter not found");
71            return;
72        }
73
74        mReceiver.register();
75        setAdapterState(mAdapter.getState());
76        updateBondedBluetoothDevices();
77    }
78
79    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
80        pw.println("BluetoothController state:");
81        pw.print("  mAdapter="); pw.println(mAdapter);
82        pw.print("  mEnabled="); pw.println(mEnabled);
83        pw.print("  mConnecting="); pw.println(mConnecting);
84        pw.print("  mLastDevice="); pw.println(mLastDevice);
85        pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
86        pw.print("  mDeviceInfo.size="); pw.println(mDeviceInfo.size());
87        for (int i = 0; i < mDeviceInfo.size(); i++) {
88            final BluetoothDevice device = mDeviceInfo.keyAt(i);
89            final DeviceInfo info = mDeviceInfo.valueAt(i);
90            pw.print("    "); pw.print(deviceToString(device));
91            pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
92            pw.print("    "); pw.println(infoToString(info));
93        }
94    }
95
96    private static String infoToString(DeviceInfo info) {
97        return info == null ? null : ("connectionState=" +
98                connectionStateToString(info.connectionState) + ",bonded=" + info.bonded);
99    }
100
101    public void addStateChangedCallback(Callback cb) {
102        mCallbacks.add(cb);
103        fireStateChange(cb);
104    }
105
106    @Override
107    public void removeStateChangedCallback(Callback cb) {
108        mCallbacks.remove(cb);
109    }
110
111    @Override
112    public boolean isBluetoothEnabled() {
113        return mAdapter != null && mAdapter.isEnabled();
114    }
115
116    @Override
117    public boolean isBluetoothConnected() {
118        return mAdapter != null
119                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
120    }
121
122    @Override
123    public boolean isBluetoothConnecting() {
124        return mAdapter != null
125                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
126    }
127
128    @Override
129    public void setBluetoothEnabled(boolean enabled) {
130        if (mAdapter != null) {
131            if (enabled) {
132                mAdapter.enable();
133            } else {
134                mAdapter.disable();
135            }
136        }
137    }
138
139    @Override
140    public boolean isBluetoothSupported() {
141        return mAdapter != null;
142    }
143
144    @Override
145    public ArraySet<PairedDevice> getPairedDevices() {
146        final ArraySet<PairedDevice> rt = new ArraySet<>();
147        for (int i = 0; i < mDeviceInfo.size(); i++) {
148            final BluetoothDevice device = mDeviceInfo.keyAt(i);
149            final DeviceInfo info = mDeviceInfo.valueAt(i);
150            if (!info.bonded) continue;
151            final PairedDevice paired = new PairedDevice();
152            paired.id = device.getAddress();
153            paired.tag = device;
154            paired.name = device.getAliasName();
155            paired.state = connectionStateToPairedDeviceState(info.connectionState);
156            rt.add(paired);
157        }
158        return rt;
159    }
160
161    private static int connectionStateToPairedDeviceState(int state) {
162        if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
163        if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
164        if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
165        return PairedDevice.STATE_DISCONNECTED;
166    }
167
168    @Override
169    public void connect(final PairedDevice pd) {
170        connect(pd, true);
171    }
172
173    @Override
174    public void disconnect(PairedDevice pd) {
175        connect(pd, false);
176    }
177
178    private void connect(PairedDevice pd, final boolean connect) {
179        if (mAdapter == null || pd == null || pd.tag == null) return;
180        final BluetoothDevice device = (BluetoothDevice) pd.tag;
181        final String action = connect ? "connect" : "disconnect";
182        if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
183        final ParcelUuid[] uuids = device.getUuids();
184        if (uuids == null) {
185            Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
186            return;
187        }
188        final SparseBooleanArray profiles = new SparseBooleanArray();
189        for (ParcelUuid uuid : uuids) {
190            final int profile = uuidToProfile(uuid);
191            if (profile == 0) {
192                Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
193                        + uuidToString(uuid));
194                continue;
195            }
196            final int profileState = mAdapter.getProfileConnectionState(profile);
197            if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
198                    + " state = " + profileStateToString(profileState));
199            final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
200            if (connect != connected) {
201                profiles.put(profile, true);
202            }
203        }
204        for (int i = 0; i < profiles.size(); i++) {
205            final int profile = profiles.keyAt(i);
206            mAdapter.getProfileProxy(mContext, new ServiceListener() {
207                @Override
208                public void onServiceConnected(int profile, BluetoothProfile proxy) {
209                    if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
210                    final Profile p = BluetoothUtil.getProfile(proxy);
211                    if (p == null) {
212                        Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
213                    } else {
214                        final boolean ok = connect ? p.connect(device) : p.disconnect(device);
215                        if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
216                                + (ok ? "succeeded" : "failed"));
217                    }
218                }
219
220                @Override
221                public void onServiceDisconnected(int profile) {
222                    if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
223                }
224            }, profile);
225        }
226    }
227
228    @Override
229    public String getLastDeviceName() {
230        return mLastDevice != null ? mLastDevice.getAliasName() : null;
231    }
232
233    private void updateBondedBluetoothDevices() {
234        if (mAdapter == null) return;
235        final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
236        for (DeviceInfo info : mDeviceInfo.values()) {
237            info.bonded = false;
238        }
239        int bondedCount = 0;
240        BluetoothDevice lastBonded = null;
241        if (bondedDevices != null) {
242            for (BluetoothDevice bondedDevice : bondedDevices) {
243                final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
244                updateInfo(bondedDevice).bonded = bonded;
245                if (bonded) {
246                    bondedCount++;
247                    lastBonded = bondedDevice;
248                }
249            }
250        }
251        if (mLastDevice == null && bondedCount == 1) {
252            mLastDevice = lastBonded;
253        }
254        firePairedDevicesChanged();
255    }
256
257    private void firePairedDevicesChanged() {
258        for (Callback cb : mCallbacks) {
259            cb.onBluetoothPairedDevicesChanged();
260        }
261    }
262
263    private void setAdapterState(int adapterState) {
264        final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
265        if (mEnabled == enabled) return;
266        mEnabled = enabled;
267        fireStateChange();
268    }
269
270    private void setConnecting(boolean connecting) {
271        if (mConnecting == connecting) return;
272        mConnecting = connecting;
273        fireStateChange();
274    }
275
276    private void fireStateChange() {
277        for (Callback cb : mCallbacks) {
278            fireStateChange(cb);
279        }
280    }
281
282    private void fireStateChange(Callback cb) {
283        cb.onBluetoothStateChange(mEnabled, mConnecting);
284    }
285
286    private final class Receiver extends BroadcastReceiver {
287        public void register() {
288            final IntentFilter filter = new IntentFilter();
289            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
290            filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
291            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
292            filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
293            mContext.registerReceiver(this, filter);
294        }
295
296        @Override
297        public void onReceive(Context context, Intent intent) {
298            final String action = intent.getAction();
299            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
300            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
301                setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
302                if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
303            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
304                final DeviceInfo info = updateInfo(device);
305                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
306                        ERROR);
307                if (state != ERROR) {
308                    info.connectionState = state;
309                }
310                mLastDevice = device;
311                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
312                        + connectionStateToString(state) + " " + deviceToString(device));
313                setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
314            } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
315                updateInfo(device);
316                mLastDevice = device;
317            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
318                if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
319                // we'll update all bonded devices below
320            }
321            updateBondedBluetoothDevices();
322        }
323    }
324
325    private DeviceInfo updateInfo(BluetoothDevice device) {
326        DeviceInfo info = mDeviceInfo.get(device);
327        info = info != null ? info : new DeviceInfo();
328        mDeviceInfo.put(device, info);
329        return info;
330    }
331
332    private static class DeviceInfo {
333        int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
334        boolean bonded;  // per getBondedDevices
335    }
336}
337