blob: a332332131012d0b3ba5c9f01554f7a3f5cf890e [file] [log] [blame]
Jason Monk7ce96b92015-02-02 11:27:58 -05001/*
2 * Copyright (C) 2011 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.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothClass;
21import android.bluetooth.BluetoothDevice;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.util.Log;
27
28import com.android.settingslib.R;
29
30import java.util.ArrayList;
31import java.util.Collection;
32import java.util.HashMap;
33import java.util.Map;
34import java.util.Set;
35
36/**
37 * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
38 * API and dispatches the event on the UI thread to the right class in the
39 * Settings.
40 */
41public final class BluetoothEventManager {
42 private static final String TAG = "BluetoothEventManager";
43
44 private final LocalBluetoothAdapter mLocalAdapter;
45 private final CachedBluetoothDeviceManager mDeviceManager;
46 private LocalBluetoothProfileManager mProfileManager;
47 private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
48 private final Map<String, Handler> mHandlerMap;
49 private Context mContext;
50
51 private final Collection<BluetoothCallback> mCallbacks =
52 new ArrayList<BluetoothCallback>();
53
Jason Monk744cf642015-05-19 12:04:41 -040054 private android.os.Handler mReceiverHandler;
55
Jason Monk7ce96b92015-02-02 11:27:58 -050056 interface Handler {
57 void onReceive(Context context, Intent intent, BluetoothDevice device);
58 }
59
Jason Monk744cf642015-05-19 12:04:41 -040060 private void addHandler(String action, Handler handler) {
Jason Monk7ce96b92015-02-02 11:27:58 -050061 mHandlerMap.put(action, handler);
62 mAdapterIntentFilter.addAction(action);
63 }
64
65 void addProfileHandler(String action, Handler handler) {
66 mHandlerMap.put(action, handler);
67 mProfileIntentFilter.addAction(action);
68 }
69
70 // Set profile manager after construction due to circular dependency
71 void setProfileManager(LocalBluetoothProfileManager manager) {
72 mProfileManager = manager;
73 }
74
75 BluetoothEventManager(LocalBluetoothAdapter adapter,
76 CachedBluetoothDeviceManager deviceManager, Context context) {
77 mLocalAdapter = adapter;
78 mDeviceManager = deviceManager;
79 mAdapterIntentFilter = new IntentFilter();
80 mProfileIntentFilter = new IntentFilter();
81 mHandlerMap = new HashMap<String, Handler>();
82 mContext = context;
83
84 // Bluetooth on/off broadcasts
85 addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
Jason Monkbe3c5db2015-02-04 13:00:55 -050086 // Generic connected/not broadcast
87 addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
88 new ConnectionStateChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -050089
90 // Discovery broadcasts
91 addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
92 addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
93 addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
94 addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
95 addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
Jason Monk8c495be2015-06-03 16:00:24 -040096 addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -050097
98 // Pairing broadcasts
99 addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
100 addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
101
102 // Fine-grained state broadcasts
103 addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
104 addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
105
106 // Dock event broadcasts
107 addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
108
Jason Monk744cf642015-05-19 12:04:41 -0400109 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
Jason Monk7ce96b92015-02-02 11:27:58 -0500110 }
111
112 void registerProfileIntentReceiver() {
Jason Monk744cf642015-05-19 12:04:41 -0400113 mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
114 }
115
116 public void setReceiverHandler(android.os.Handler handler) {
117 mContext.unregisterReceiver(mBroadcastReceiver);
118 mReceiverHandler = handler;
119 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
120 registerProfileIntentReceiver();
Jason Monk7ce96b92015-02-02 11:27:58 -0500121 }
122
123 /** Register to start receiving callbacks for Bluetooth events. */
124 public void registerCallback(BluetoothCallback callback) {
125 synchronized (mCallbacks) {
126 mCallbacks.add(callback);
127 }
128 }
129
130 /** Unregister to stop receiving callbacks for Bluetooth events. */
131 public void unregisterCallback(BluetoothCallback callback) {
132 synchronized (mCallbacks) {
133 mCallbacks.remove(callback);
134 }
135 }
136
137 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
138 @Override
139 public void onReceive(Context context, Intent intent) {
140 String action = intent.getAction();
141 BluetoothDevice device = intent
142 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
143
144 Handler handler = mHandlerMap.get(action);
145 if (handler != null) {
146 handler.onReceive(context, intent, device);
147 }
148 }
149 };
150
151 private class AdapterStateChangedHandler implements Handler {
152 public void onReceive(Context context, Intent intent,
153 BluetoothDevice device) {
154 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
155 BluetoothAdapter.ERROR);
156 // update local profiles and get paired devices
157 mLocalAdapter.setBluetoothStateInt(state);
158 // send callback to update UI and possibly start scanning
159 synchronized (mCallbacks) {
160 for (BluetoothCallback callback : mCallbacks) {
161 callback.onBluetoothStateChanged(state);
162 }
163 }
164 // Inform CachedDeviceManager that the adapter state has changed
165 mDeviceManager.onBluetoothStateChanged(state);
166 }
167 }
168
169 private class ScanningStateChangedHandler implements Handler {
170 private final boolean mStarted;
171
172 ScanningStateChangedHandler(boolean started) {
173 mStarted = started;
174 }
175 public void onReceive(Context context, Intent intent,
176 BluetoothDevice device) {
177 synchronized (mCallbacks) {
178 for (BluetoothCallback callback : mCallbacks) {
179 callback.onScanningStateChanged(mStarted);
180 }
181 }
182 mDeviceManager.onScanningStateChanged(mStarted);
183 }
184 }
185
186 private class DeviceFoundHandler implements Handler {
187 public void onReceive(Context context, Intent intent,
188 BluetoothDevice device) {
189 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
190 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
191 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
192 // TODO Pick up UUID. They should be available for 2.1 devices.
193 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
194 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
195 if (cachedDevice == null) {
196 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
197 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
198 + cachedDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -0500199 }
200 cachedDevice.setRssi(rssi);
201 cachedDevice.setBtClass(btClass);
202 cachedDevice.setNewName(name);
203 cachedDevice.setVisible(true);
204 }
205 }
206
Jason Monkbe3c5db2015-02-04 13:00:55 -0500207 private class ConnectionStateChangedHandler implements Handler {
208 @Override
209 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
210 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
211 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
212 BluetoothAdapter.ERROR);
213 dispatchConnectionStateChanged(cachedDevice, state);
214 }
215 }
216
217 private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
218 synchronized (mCallbacks) {
219 for (BluetoothCallback callback : mCallbacks) {
220 callback.onConnectionStateChanged(cachedDevice, state);
221 }
222 }
223 }
224
225 void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
Jason Monk7ce96b92015-02-02 11:27:58 -0500226 synchronized (mCallbacks) {
227 for (BluetoothCallback callback : mCallbacks) {
228 callback.onDeviceAdded(cachedDevice);
229 }
230 }
231 }
232
233 private class DeviceDisappearedHandler implements Handler {
234 public void onReceive(Context context, Intent intent,
235 BluetoothDevice device) {
236 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
237 if (cachedDevice == null) {
238 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
239 return;
240 }
241 if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
242 synchronized (mCallbacks) {
243 for (BluetoothCallback callback : mCallbacks) {
244 callback.onDeviceDeleted(cachedDevice);
245 }
246 }
247 }
248 }
249 }
250
251 private class NameChangedHandler implements Handler {
252 public void onReceive(Context context, Intent intent,
253 BluetoothDevice device) {
254 mDeviceManager.onDeviceNameUpdated(device);
255 }
256 }
257
258 private class BondStateChangedHandler implements Handler {
259 public void onReceive(Context context, Intent intent,
260 BluetoothDevice device) {
261 if (device == null) {
262 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
263 return;
264 }
265 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
266 BluetoothDevice.ERROR);
267 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
268 if (cachedDevice == null) {
269 Log.w(TAG, "CachedBluetoothDevice for device " + device +
270 " not found, calling readPairedDevices().");
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700271 if (readPairedDevices()) {
272 cachedDevice = mDeviceManager.findDevice(device);
Jason Monk7ce96b92015-02-02 11:27:58 -0500273 }
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700274
Jason Monk7ce96b92015-02-02 11:27:58 -0500275 if (cachedDevice == null) {
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700276 Log.w(TAG, "Got bonding state changed for " + device +
277 ", but we have no record of that device.");
278
279 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
280 dispatchDeviceAdded(cachedDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -0500281 }
282 }
283
284 synchronized (mCallbacks) {
285 for (BluetoothCallback callback : mCallbacks) {
286 callback.onDeviceBondStateChanged(cachedDevice, bondState);
287 }
288 }
289 cachedDevice.onBondingStateChanged(bondState);
290
291 if (bondState == BluetoothDevice.BOND_NONE) {
292 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
293 BluetoothDevice.ERROR);
294
295 showUnbondMessage(context, cachedDevice.getName(), reason);
296 }
297 }
298
299 /**
300 * Called when we have reached the unbonded state.
301 *
302 * @param reason one of the error reasons from
303 * BluetoothDevice.UNBOND_REASON_*
304 */
305 private void showUnbondMessage(Context context, String name, int reason) {
306 int errorMsg;
307
308 switch(reason) {
309 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
310 errorMsg = R.string.bluetooth_pairing_pin_error_message;
311 break;
312 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
313 errorMsg = R.string.bluetooth_pairing_rejected_error_message;
314 break;
315 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
316 errorMsg = R.string.bluetooth_pairing_device_down_error_message;
317 break;
318 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
319 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
320 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
321 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
322 errorMsg = R.string.bluetooth_pairing_error_message;
323 break;
324 default:
325 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
326 return;
327 }
328 Utils.showError(context, name, errorMsg);
329 }
330 }
331
332 private class ClassChangedHandler implements Handler {
333 public void onReceive(Context context, Intent intent,
334 BluetoothDevice device) {
335 mDeviceManager.onBtClassChanged(device);
336 }
337 }
338
339 private class UuidChangedHandler implements Handler {
340 public void onReceive(Context context, Intent intent,
341 BluetoothDevice device) {
342 mDeviceManager.onUuidChanged(device);
343 }
344 }
345
346 private class PairingCancelHandler implements Handler {
347 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
348 if (device == null) {
349 Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
350 return;
351 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500352 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
Pavlin Radoslavovd2b033ae2015-06-04 11:46:22 -0700353 if (cachedDevice == null) {
354 Log.e(TAG, "ACTION_PAIRING_CANCEL with no cached device");
355 return;
356 }
357 int errorMsg = R.string.bluetooth_pairing_error_message;
Sanket Padawe4d08a482015-07-07 14:12:15 -0700358 if (context != null && cachedDevice != null) {
359 Utils.showError(context, cachedDevice.getName(), errorMsg);
360 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500361 }
362 }
363
364 private class DockEventHandler implements Handler {
365 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
366 // Remove if unpair device upon undocking
367 int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
368 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
369 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
370 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
371 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
372 if (cachedDevice != null) {
373 cachedDevice.setVisible(false);
374 }
375 }
376 }
377 }
378 }
379 boolean readPairedDevices() {
380 Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
381 if (bondedDevices == null) {
382 return false;
383 }
384
385 boolean deviceAdded = false;
386 for (BluetoothDevice device : bondedDevices) {
387 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
388 if (cachedDevice == null) {
389 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
390 dispatchDeviceAdded(cachedDevice);
391 deviceAdded = true;
392 }
393 }
394
395 return deviceAdded;
396 }
397}