blob: 784c714f0b0105e91e971306c2acf607ecc4967d [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
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -080019import android.bluetooth.BluetoothA2dp;
Jason Monk7ce96b92015-02-02 11:27:58 -050020import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothClass;
22import android.bluetooth.BluetoothDevice;
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -080023import android.bluetooth.BluetoothHeadset;
24import android.bluetooth.BluetoothProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050025import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.util.Log;
30
31import com.android.settingslib.R;
32
33import java.util.ArrayList;
34import java.util.Collection;
35import java.util.HashMap;
36import java.util.Map;
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -080037import java.util.Objects;
Jason Monk7ce96b92015-02-02 11:27:58 -050038import java.util.Set;
39
40/**
41 * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
42 * API and dispatches the event on the UI thread to the right class in the
43 * Settings.
44 */
Fan Zhang82dd3b02016-12-27 13:13:00 -080045public class BluetoothEventManager {
Jason Monk7ce96b92015-02-02 11:27:58 -050046 private static final String TAG = "BluetoothEventManager";
47
48 private final LocalBluetoothAdapter mLocalAdapter;
49 private final CachedBluetoothDeviceManager mDeviceManager;
50 private LocalBluetoothProfileManager mProfileManager;
51 private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
52 private final Map<String, Handler> mHandlerMap;
53 private Context mContext;
54
55 private final Collection<BluetoothCallback> mCallbacks =
56 new ArrayList<BluetoothCallback>();
57
Jason Monk744cf642015-05-19 12:04:41 -040058 private android.os.Handler mReceiverHandler;
59
Jason Monk7ce96b92015-02-02 11:27:58 -050060 interface Handler {
61 void onReceive(Context context, Intent intent, BluetoothDevice device);
62 }
63
Jason Monk744cf642015-05-19 12:04:41 -040064 private void addHandler(String action, Handler handler) {
Jason Monk7ce96b92015-02-02 11:27:58 -050065 mHandlerMap.put(action, handler);
66 mAdapterIntentFilter.addAction(action);
67 }
68
69 void addProfileHandler(String action, Handler handler) {
70 mHandlerMap.put(action, handler);
71 mProfileIntentFilter.addAction(action);
72 }
73
74 // Set profile manager after construction due to circular dependency
75 void setProfileManager(LocalBluetoothProfileManager manager) {
76 mProfileManager = manager;
77 }
78
79 BluetoothEventManager(LocalBluetoothAdapter adapter,
80 CachedBluetoothDeviceManager deviceManager, Context context) {
81 mLocalAdapter = adapter;
82 mDeviceManager = deviceManager;
83 mAdapterIntentFilter = new IntentFilter();
84 mProfileIntentFilter = new IntentFilter();
85 mHandlerMap = new HashMap<String, Handler>();
86 mContext = context;
87
88 // Bluetooth on/off broadcasts
89 addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
Jason Monkbe3c5db2015-02-04 13:00:55 -050090 // Generic connected/not broadcast
91 addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
92 new ConnectionStateChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -050093
94 // Discovery broadcasts
95 addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
96 addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
97 addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
98 addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
99 addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
Jason Monk8c495be2015-06-03 16:00:24 -0400100 addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -0500101
102 // Pairing broadcasts
103 addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -0500104
105 // Fine-grained state broadcasts
106 addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
107 addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
Jack He6258aae2017-06-29 17:01:23 -0700108 addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -0500109
110 // Dock event broadcasts
111 addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
112
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800113 // Active device broadcasts
114 addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED,
115 new ActiveDeviceChangedHandler());
116 addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
117 new ActiveDeviceChangedHandler());
118
Jason Monk744cf642015-05-19 12:04:41 -0400119 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
Manu Viswanadhand1992952016-10-25 20:38:23 +0530120 mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
Jason Monk7ce96b92015-02-02 11:27:58 -0500121 }
122
123 void registerProfileIntentReceiver() {
Manu Viswanadhand1992952016-10-25 20:38:23 +0530124 mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
Jason Monk744cf642015-05-19 12:04:41 -0400125 }
126
127 public void setReceiverHandler(android.os.Handler handler) {
128 mContext.unregisterReceiver(mBroadcastReceiver);
Manu Viswanadhand1992952016-10-25 20:38:23 +0530129 mContext.unregisterReceiver(mProfileBroadcastReceiver);
Jason Monk744cf642015-05-19 12:04:41 -0400130 mReceiverHandler = handler;
131 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
132 registerProfileIntentReceiver();
Jason Monk7ce96b92015-02-02 11:27:58 -0500133 }
134
135 /** Register to start receiving callbacks for Bluetooth events. */
136 public void registerCallback(BluetoothCallback callback) {
137 synchronized (mCallbacks) {
138 mCallbacks.add(callback);
139 }
140 }
141
142 /** Unregister to stop receiving callbacks for Bluetooth events. */
143 public void unregisterCallback(BluetoothCallback callback) {
144 synchronized (mCallbacks) {
145 mCallbacks.remove(callback);
146 }
147 }
148
149 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
150 @Override
151 public void onReceive(Context context, Intent intent) {
152 String action = intent.getAction();
153 BluetoothDevice device = intent
154 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
155
156 Handler handler = mHandlerMap.get(action);
157 if (handler != null) {
158 handler.onReceive(context, intent, device);
159 }
160 }
161 };
162
Manu Viswanadhand1992952016-10-25 20:38:23 +0530163 private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
164 @Override
165 public void onReceive(Context context, Intent intent) {
166 String action = intent.getAction();
167 BluetoothDevice device = intent
168 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
169
170 Handler handler = mHandlerMap.get(action);
171 if (handler != null) {
172 handler.onReceive(context, intent, device);
173 }
174 }
175 };
176
Jason Monk7ce96b92015-02-02 11:27:58 -0500177 private class AdapterStateChangedHandler implements Handler {
178 public void onReceive(Context context, Intent intent,
179 BluetoothDevice device) {
180 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
181 BluetoothAdapter.ERROR);
Manu Viswanadhand1992952016-10-25 20:38:23 +0530182 // Reregister Profile Broadcast Receiver as part of TURN OFF
183 if (state == BluetoothAdapter.STATE_OFF)
184 {
185 context.unregisterReceiver(mProfileBroadcastReceiver);
186 registerProfileIntentReceiver();
187 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500188 // update local profiles and get paired devices
189 mLocalAdapter.setBluetoothStateInt(state);
190 // send callback to update UI and possibly start scanning
191 synchronized (mCallbacks) {
192 for (BluetoothCallback callback : mCallbacks) {
193 callback.onBluetoothStateChanged(state);
194 }
195 }
196 // Inform CachedDeviceManager that the adapter state has changed
197 mDeviceManager.onBluetoothStateChanged(state);
198 }
199 }
200
201 private class ScanningStateChangedHandler implements Handler {
202 private final boolean mStarted;
203
204 ScanningStateChangedHandler(boolean started) {
205 mStarted = started;
206 }
207 public void onReceive(Context context, Intent intent,
208 BluetoothDevice device) {
209 synchronized (mCallbacks) {
210 for (BluetoothCallback callback : mCallbacks) {
211 callback.onScanningStateChanged(mStarted);
212 }
213 }
214 mDeviceManager.onScanningStateChanged(mStarted);
215 }
216 }
217
218 private class DeviceFoundHandler implements Handler {
219 public void onReceive(Context context, Intent intent,
220 BluetoothDevice device) {
221 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
222 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
223 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
224 // TODO Pick up UUID. They should be available for 2.1 devices.
225 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
226 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
227 if (cachedDevice == null) {
228 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
229 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
230 + cachedDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -0500231 }
232 cachedDevice.setRssi(rssi);
233 cachedDevice.setBtClass(btClass);
234 cachedDevice.setNewName(name);
Jack He51520472017-07-24 12:30:08 -0700235 cachedDevice.setJustDiscovered(true);
Jason Monk7ce96b92015-02-02 11:27:58 -0500236 }
237 }
238
Jason Monkbe3c5db2015-02-04 13:00:55 -0500239 private class ConnectionStateChangedHandler implements Handler {
240 @Override
241 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
242 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
243 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
244 BluetoothAdapter.ERROR);
245 dispatchConnectionStateChanged(cachedDevice, state);
246 }
247 }
248
249 private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
250 synchronized (mCallbacks) {
251 for (BluetoothCallback callback : mCallbacks) {
252 callback.onConnectionStateChanged(cachedDevice, state);
253 }
254 }
255 }
256
257 void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
Jason Monk7ce96b92015-02-02 11:27:58 -0500258 synchronized (mCallbacks) {
259 for (BluetoothCallback callback : mCallbacks) {
260 callback.onDeviceAdded(cachedDevice);
261 }
262 }
263 }
264
265 private class DeviceDisappearedHandler implements Handler {
266 public void onReceive(Context context, Intent intent,
267 BluetoothDevice device) {
268 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
269 if (cachedDevice == null) {
270 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
271 return;
272 }
273 if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
274 synchronized (mCallbacks) {
275 for (BluetoothCallback callback : mCallbacks) {
276 callback.onDeviceDeleted(cachedDevice);
277 }
278 }
279 }
280 }
281 }
282
283 private class NameChangedHandler implements Handler {
284 public void onReceive(Context context, Intent intent,
285 BluetoothDevice device) {
286 mDeviceManager.onDeviceNameUpdated(device);
287 }
288 }
289
290 private class BondStateChangedHandler implements Handler {
291 public void onReceive(Context context, Intent intent,
292 BluetoothDevice device) {
293 if (device == null) {
294 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
295 return;
296 }
297 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
298 BluetoothDevice.ERROR);
299 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
300 if (cachedDevice == null) {
301 Log.w(TAG, "CachedBluetoothDevice for device " + device +
302 " not found, calling readPairedDevices().");
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700303 if (readPairedDevices()) {
304 cachedDevice = mDeviceManager.findDevice(device);
Jason Monk7ce96b92015-02-02 11:27:58 -0500305 }
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700306
Jason Monk7ce96b92015-02-02 11:27:58 -0500307 if (cachedDevice == null) {
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700308 Log.w(TAG, "Got bonding state changed for " + device +
309 ", but we have no record of that device.");
310
311 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
312 dispatchDeviceAdded(cachedDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -0500313 }
314 }
315
316 synchronized (mCallbacks) {
317 for (BluetoothCallback callback : mCallbacks) {
318 callback.onDeviceBondStateChanged(cachedDevice, bondState);
319 }
320 }
321 cachedDevice.onBondingStateChanged(bondState);
322
323 if (bondState == BluetoothDevice.BOND_NONE) {
324 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
325 BluetoothDevice.ERROR);
326
327 showUnbondMessage(context, cachedDevice.getName(), reason);
328 }
329 }
330
331 /**
332 * Called when we have reached the unbonded state.
333 *
334 * @param reason one of the error reasons from
335 * BluetoothDevice.UNBOND_REASON_*
336 */
337 private void showUnbondMessage(Context context, String name, int reason) {
338 int errorMsg;
339
340 switch(reason) {
341 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
342 errorMsg = R.string.bluetooth_pairing_pin_error_message;
343 break;
344 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
345 errorMsg = R.string.bluetooth_pairing_rejected_error_message;
346 break;
347 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
348 errorMsg = R.string.bluetooth_pairing_device_down_error_message;
349 break;
350 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
351 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
352 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
353 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
354 errorMsg = R.string.bluetooth_pairing_error_message;
355 break;
356 default:
357 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
358 return;
359 }
360 Utils.showError(context, name, errorMsg);
361 }
362 }
363
364 private class ClassChangedHandler implements Handler {
365 public void onReceive(Context context, Intent intent,
366 BluetoothDevice device) {
367 mDeviceManager.onBtClassChanged(device);
368 }
369 }
370
371 private class UuidChangedHandler implements Handler {
372 public void onReceive(Context context, Intent intent,
373 BluetoothDevice device) {
374 mDeviceManager.onUuidChanged(device);
375 }
376 }
377
Jason Monk7ce96b92015-02-02 11:27:58 -0500378 private class DockEventHandler implements Handler {
379 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
380 // Remove if unpair device upon undocking
381 int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
382 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
383 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
384 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
385 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
386 if (cachedDevice != null) {
Jack He51520472017-07-24 12:30:08 -0700387 cachedDevice.setJustDiscovered(false);
Jason Monk7ce96b92015-02-02 11:27:58 -0500388 }
389 }
390 }
391 }
392 }
Jack He6258aae2017-06-29 17:01:23 -0700393
394 private class BatteryLevelChangedHandler implements Handler {
395 public void onReceive(Context context, Intent intent,
396 BluetoothDevice device) {
397 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
398 if (cachedDevice != null) {
399 cachedDevice.refresh();
400 }
401 }
402 }
403
Jason Monk7ce96b92015-02-02 11:27:58 -0500404 boolean readPairedDevices() {
405 Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
406 if (bondedDevices == null) {
407 return false;
408 }
409
410 boolean deviceAdded = false;
411 for (BluetoothDevice device : bondedDevices) {
412 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
413 if (cachedDevice == null) {
414 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
415 dispatchDeviceAdded(cachedDevice);
416 deviceAdded = true;
417 }
418 }
419
420 return deviceAdded;
421 }
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800422
423 private class ActiveDeviceChangedHandler implements Handler {
424 @Override
425 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
426 String action = intent.getAction();
427 if (action == null) {
428 Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
429 return;
430 }
431 CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
432 int bluetoothProfile = 0;
433 if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
434 bluetoothProfile = BluetoothProfile.A2DP;
435 } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
436 bluetoothProfile = BluetoothProfile.HEADSET;
437 } else {
438 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
439 return;
440 }
441 dispatchActiveDeviceChanged(activeDevice, bluetoothProfile);
442 }
443 }
444
445 private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
446 int bluetoothProfile) {
Pavlin Radoslavovc285d552018-02-06 16:14:00 -0800447 mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800448 synchronized (mCallbacks) {
449 for (BluetoothCallback callback : mCallbacks) {
450 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
451 }
452 }
453 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500454}