blob: f02162364de66c79bc0b6af7f114fda7fcc0da16 [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
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
John Spurlock486b78e2014-07-07 08:37:56 -040019import 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
John Spurlockaf8d6c42014-05-07 17:49:08 -040028import android.bluetooth.BluetoothAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -040029import android.bluetooth.BluetoothDevice;
John Spurlock486b78e2014-07-07 08:37:56 -040030import android.bluetooth.BluetoothManager;
31import android.bluetooth.BluetoothProfile;
32import android.bluetooth.BluetoothProfile.ServiceListener;
John Spurlockaf8d6c42014-05-07 17:49:08 -040033import android.content.BroadcastReceiver;
34import android.content.Context;
35import android.content.Intent;
36import android.content.IntentFilter;
John Spurlock486b78e2014-07-07 08:37:56 -040037import android.os.ParcelUuid;
38import android.util.ArrayMap;
39import android.util.ArraySet;
40import android.util.Log;
41import android.util.SparseBooleanArray;
John Spurlockaf8d6c42014-05-07 17:49:08 -040042
John Spurlock486b78e2014-07-07 08:37:56 -040043import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
44
45import java.io.FileDescriptor;
46import java.io.PrintWriter;
John Spurlockaf8d6c42014-05-07 17:49:08 -040047import java.util.ArrayList;
John Spurlockaf8d6c42014-05-07 17:49:08 -040048import java.util.Set;
49
John Spurlock486b78e2014-07-07 08:37:56 -040050public class BluetoothControllerImpl implements BluetoothController {
51 private static final String TAG = "BluetoothController";
52 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
John Spurlockaf8d6c42014-05-07 17:49:08 -040053
John Spurlock486b78e2014-07-07 08:37:56 -040054 private final Context mContext;
John Spurlockd1c86e22014-06-01 00:04:53 -040055 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
John Spurlockaf8d6c42014-05-07 17:49:08 -040056 private final BluetoothAdapter mAdapter;
John Spurlock486b78e2014-07-07 08:37:56 -040057 private final Receiver mReceiver = new Receiver();
58 private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
John Spurlockaf8d6c42014-05-07 17:49:08 -040059
John Spurlockd1c86e22014-06-01 00:04:53 -040060 private boolean mEnabled;
61 private boolean mConnecting;
62 private BluetoothDevice mLastDevice;
John Spurlockaf8d6c42014-05-07 17:49:08 -040063
64 public BluetoothControllerImpl(Context context) {
John Spurlock486b78e2014-07-07 08:37:56 -040065 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;
John Spurlockaf8d6c42014-05-07 17:49:08 -040072 }
John Spurlock486b78e2014-07-07 08:37:56 -040073
74 mReceiver.register();
75 setAdapterState(mAdapter.getState());
John Spurlockaf8d6c42014-05-07 17:49:08 -040076 updateBondedBluetoothDevices();
77 }
78
John Spurlock486b78e2014-07-07 08:37:56 -040079 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
John Spurlockd1c86e22014-06-01 00:04:53 -0400101 public void addStateChangedCallback(Callback cb) {
102 mCallbacks.add(cb);
John Spurlock486b78e2014-07-07 08:37:56 -0400103 fireStateChange(cb);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400104 }
105
106 @Override
John Spurlockd1c86e22014-06-01 00:04:53 -0400107 public void removeStateChangedCallback(Callback cb) {
108 mCallbacks.remove(cb);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400109 }
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
John Spurlockd1c86e22014-06-01 00:04:53 -0400123 public boolean isBluetoothConnecting() {
124 return mAdapter != null
125 && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
126 }
127
128 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400129 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
John Spurlock486b78e2014-07-07 08:37:56 -0400144 @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 SparseBooleanArray profiles = new SparseBooleanArray();
184 for (ParcelUuid uuid : device.getUuids()) {
185 final int profile = uuidToProfile(uuid);
186 if (profile == 0) {
187 Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
188 + uuidToString(uuid));
189 continue;
190 }
191 final int profileState = mAdapter.getProfileConnectionState(profile);
192 if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
193 + " state = " + profileStateToString(profileState));
194 final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
195 if (connect != connected) {
196 profiles.put(profile, true);
197 }
198 }
199 for (int i = 0; i < profiles.size(); i++) {
200 final int profile = profiles.keyAt(i);
201 mAdapter.getProfileProxy(mContext, new ServiceListener() {
202 @Override
203 public void onServiceConnected(int profile, BluetoothProfile proxy) {
204 if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
205 final Profile p = BluetoothUtil.getProfile(proxy);
206 if (p == null) {
207 Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
208 } else {
209 final boolean ok = connect ? p.connect(device) : p.disconnect(device);
210 if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
211 + (ok ? "succeeded" : "failed"));
212 }
213 }
214
215 @Override
216 public void onServiceDisconnected(int profile) {
217 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
218 }
219 }, profile);
220 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400221 }
222
223 @Override
John Spurlockd1c86e22014-06-01 00:04:53 -0400224 public String getLastDeviceName() {
John Spurlock486b78e2014-07-07 08:37:56 -0400225 return mLastDevice != null ? mLastDevice.getAliasName() : null;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400226 }
227
228 private void updateBondedBluetoothDevices() {
John Spurlock486b78e2014-07-07 08:37:56 -0400229 if (mAdapter == null) return;
230 final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
231 for (DeviceInfo info : mDeviceInfo.values()) {
232 info.bonded = false;
233 }
234 int bondedCount = 0;
235 BluetoothDevice lastBonded = null;
236 if (bondedDevices != null) {
237 for (BluetoothDevice bondedDevice : bondedDevices) {
238 final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
239 updateInfo(bondedDevice).bonded = bonded;
240 if (bonded) {
241 bondedCount++;
242 lastBonded = bondedDevice;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400243 }
244 }
245 }
John Spurlock486b78e2014-07-07 08:37:56 -0400246 if (mLastDevice == null && bondedCount == 1) {
247 mLastDevice = lastBonded;
248 }
249 firePairedDevicesChanged();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400250 }
251
John Spurlock486b78e2014-07-07 08:37:56 -0400252 private void firePairedDevicesChanged() {
John Spurlockd1c86e22014-06-01 00:04:53 -0400253 for (Callback cb : mCallbacks) {
John Spurlock486b78e2014-07-07 08:37:56 -0400254 cb.onBluetoothPairedDevicesChanged();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400255 }
256 }
John Spurlockccb6b9a2014-05-17 15:54:40 -0400257
John Spurlock486b78e2014-07-07 08:37:56 -0400258 private void setAdapterState(int adapterState) {
259 final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
260 if (mEnabled == enabled) return;
261 mEnabled = enabled;
262 fireStateChange();
263 }
264
265 private void setConnecting(boolean connecting) {
266 if (mConnecting == connecting) return;
267 mConnecting = connecting;
268 fireStateChange();
269 }
270
271 private void fireStateChange() {
272 for (Callback cb : mCallbacks) {
273 fireStateChange(cb);
274 }
275 }
276
277 private void fireStateChange(Callback cb) {
John Spurlockd1c86e22014-06-01 00:04:53 -0400278 cb.onBluetoothStateChange(mEnabled, mConnecting);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400279 }
John Spurlock486b78e2014-07-07 08:37:56 -0400280
281 private final class Receiver extends BroadcastReceiver {
282 public void register() {
283 final IntentFilter filter = new IntentFilter();
284 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
285 filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
286 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
287 filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
288 mContext.registerReceiver(this, filter);
289 }
290
291 @Override
292 public void onReceive(Context context, Intent intent) {
293 final String action = intent.getAction();
294 final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
295 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
296 setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
297 if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
298 } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
299 final DeviceInfo info = updateInfo(device);
300 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
301 ERROR);
302 if (state != ERROR) {
303 info.connectionState = state;
304 }
305 mLastDevice = device;
306 if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
307 + connectionStateToString(state) + " " + deviceToString(device));
308 setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
309 } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
310 updateInfo(device);
311 mLastDevice = device;
312 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
313 if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
314 // we'll update all bonded devices below
315 }
316 updateBondedBluetoothDevices();
317 }
318 }
319
320 private DeviceInfo updateInfo(BluetoothDevice device) {
321 DeviceInfo info = mDeviceInfo.get(device);
322 info = info != null ? info : new DeviceInfo();
323 mDeviceInfo.put(device, info);
324 return info;
325 }
326
327 private static class DeviceInfo {
328 int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
329 boolean bonded; // per getBondedDevices
330 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400331}