blob: b74b2cd03a52e6504e5e4c101b0bfdd40a22c6eb [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;
Hansong Zhangd7b35912018-03-16 09:15:48 -070024import android.bluetooth.BluetoothHearingAid;
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -080025import android.bluetooth.BluetoothProfile;
Jason Monk7ce96b92015-02-02 11:27:58 -050026import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.util.Log;
31
32import com.android.settingslib.R;
33
34import java.util.ArrayList;
35import java.util.Collection;
36import java.util.HashMap;
37import java.util.Map;
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -080038import java.util.Objects;
Jason Monk7ce96b92015-02-02 11:27:58 -050039import java.util.Set;
40
41/**
42 * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
43 * API and dispatches the event on the UI thread to the right class in the
44 * Settings.
45 */
Fan Zhang82dd3b02016-12-27 13:13:00 -080046public class BluetoothEventManager {
Jason Monk7ce96b92015-02-02 11:27:58 -050047 private static final String TAG = "BluetoothEventManager";
48
49 private final LocalBluetoothAdapter mLocalAdapter;
50 private final CachedBluetoothDeviceManager mDeviceManager;
51 private LocalBluetoothProfileManager mProfileManager;
52 private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
53 private final Map<String, Handler> mHandlerMap;
54 private Context mContext;
55
56 private final Collection<BluetoothCallback> mCallbacks =
57 new ArrayList<BluetoothCallback>();
58
Jason Monk744cf642015-05-19 12:04:41 -040059 private android.os.Handler mReceiverHandler;
60
Jason Monk7ce96b92015-02-02 11:27:58 -050061 interface Handler {
62 void onReceive(Context context, Intent intent, BluetoothDevice device);
63 }
64
Jason Monk744cf642015-05-19 12:04:41 -040065 private void addHandler(String action, Handler handler) {
Jason Monk7ce96b92015-02-02 11:27:58 -050066 mHandlerMap.put(action, handler);
67 mAdapterIntentFilter.addAction(action);
68 }
69
70 void addProfileHandler(String action, Handler handler) {
71 mHandlerMap.put(action, handler);
72 mProfileIntentFilter.addAction(action);
73 }
74
75 // Set profile manager after construction due to circular dependency
76 void setProfileManager(LocalBluetoothProfileManager manager) {
77 mProfileManager = manager;
78 }
79
80 BluetoothEventManager(LocalBluetoothAdapter adapter,
81 CachedBluetoothDeviceManager deviceManager, Context context) {
82 mLocalAdapter = adapter;
83 mDeviceManager = deviceManager;
84 mAdapterIntentFilter = new IntentFilter();
85 mProfileIntentFilter = new IntentFilter();
86 mHandlerMap = new HashMap<String, Handler>();
87 mContext = context;
88
89 // Bluetooth on/off broadcasts
90 addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
Jason Monkbe3c5db2015-02-04 13:00:55 -050091 // Generic connected/not broadcast
92 addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
93 new ConnectionStateChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -050094
95 // Discovery broadcasts
96 addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
97 addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
98 addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
99 addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
100 addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
Jason Monk8c495be2015-06-03 16:00:24 -0400101 addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -0500102
103 // Pairing broadcasts
104 addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -0500105
106 // Fine-grained state broadcasts
107 addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
108 addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
Jack He6258aae2017-06-29 17:01:23 -0700109 addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
Jason Monk7ce96b92015-02-02 11:27:58 -0500110
111 // Dock event broadcasts
112 addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
113
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800114 // Active device broadcasts
115 addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED,
116 new ActiveDeviceChangedHandler());
117 addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
118 new ActiveDeviceChangedHandler());
Hansong Zhangd7b35912018-03-16 09:15:48 -0700119 addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
120 new ActiveDeviceChangedHandler());
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800121
Jason Monk744cf642015-05-19 12:04:41 -0400122 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
Manu Viswanadhand1992952016-10-25 20:38:23 +0530123 mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
Jason Monk7ce96b92015-02-02 11:27:58 -0500124 }
125
126 void registerProfileIntentReceiver() {
Manu Viswanadhand1992952016-10-25 20:38:23 +0530127 mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
Jason Monk744cf642015-05-19 12:04:41 -0400128 }
129
130 public void setReceiverHandler(android.os.Handler handler) {
131 mContext.unregisterReceiver(mBroadcastReceiver);
Manu Viswanadhand1992952016-10-25 20:38:23 +0530132 mContext.unregisterReceiver(mProfileBroadcastReceiver);
Jason Monk744cf642015-05-19 12:04:41 -0400133 mReceiverHandler = handler;
134 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
135 registerProfileIntentReceiver();
Jason Monk7ce96b92015-02-02 11:27:58 -0500136 }
137
138 /** Register to start receiving callbacks for Bluetooth events. */
139 public void registerCallback(BluetoothCallback callback) {
140 synchronized (mCallbacks) {
141 mCallbacks.add(callback);
142 }
143 }
144
145 /** Unregister to stop receiving callbacks for Bluetooth events. */
146 public void unregisterCallback(BluetoothCallback callback) {
147 synchronized (mCallbacks) {
148 mCallbacks.remove(callback);
149 }
150 }
151
152 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
153 @Override
154 public void onReceive(Context context, Intent intent) {
155 String action = intent.getAction();
156 BluetoothDevice device = intent
157 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
158
159 Handler handler = mHandlerMap.get(action);
160 if (handler != null) {
161 handler.onReceive(context, intent, device);
162 }
163 }
164 };
165
Manu Viswanadhand1992952016-10-25 20:38:23 +0530166 private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
167 @Override
168 public void onReceive(Context context, Intent intent) {
169 String action = intent.getAction();
170 BluetoothDevice device = intent
171 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
172
173 Handler handler = mHandlerMap.get(action);
174 if (handler != null) {
175 handler.onReceive(context, intent, device);
176 }
177 }
178 };
179
Jason Monk7ce96b92015-02-02 11:27:58 -0500180 private class AdapterStateChangedHandler implements Handler {
181 public void onReceive(Context context, Intent intent,
182 BluetoothDevice device) {
183 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
184 BluetoothAdapter.ERROR);
Manu Viswanadhand1992952016-10-25 20:38:23 +0530185 // Reregister Profile Broadcast Receiver as part of TURN OFF
186 if (state == BluetoothAdapter.STATE_OFF)
187 {
188 context.unregisterReceiver(mProfileBroadcastReceiver);
189 registerProfileIntentReceiver();
190 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500191 // update local profiles and get paired devices
192 mLocalAdapter.setBluetoothStateInt(state);
193 // send callback to update UI and possibly start scanning
194 synchronized (mCallbacks) {
195 for (BluetoothCallback callback : mCallbacks) {
196 callback.onBluetoothStateChanged(state);
197 }
198 }
199 // Inform CachedDeviceManager that the adapter state has changed
200 mDeviceManager.onBluetoothStateChanged(state);
201 }
202 }
203
204 private class ScanningStateChangedHandler implements Handler {
205 private final boolean mStarted;
206
207 ScanningStateChangedHandler(boolean started) {
208 mStarted = started;
209 }
210 public void onReceive(Context context, Intent intent,
211 BluetoothDevice device) {
212 synchronized (mCallbacks) {
213 for (BluetoothCallback callback : mCallbacks) {
214 callback.onScanningStateChanged(mStarted);
215 }
216 }
217 mDeviceManager.onScanningStateChanged(mStarted);
218 }
219 }
220
221 private class DeviceFoundHandler implements Handler {
222 public void onReceive(Context context, Intent intent,
223 BluetoothDevice device) {
224 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
225 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
226 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
227 // TODO Pick up UUID. They should be available for 2.1 devices.
228 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
229 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
230 if (cachedDevice == null) {
231 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
232 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
233 + cachedDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -0500234 }
235 cachedDevice.setRssi(rssi);
236 cachedDevice.setBtClass(btClass);
237 cachedDevice.setNewName(name);
Jack He51520472017-07-24 12:30:08 -0700238 cachedDevice.setJustDiscovered(true);
Jason Monk7ce96b92015-02-02 11:27:58 -0500239 }
240 }
241
Jason Monkbe3c5db2015-02-04 13:00:55 -0500242 private class ConnectionStateChangedHandler implements Handler {
243 @Override
244 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
245 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
246 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
247 BluetoothAdapter.ERROR);
248 dispatchConnectionStateChanged(cachedDevice, state);
249 }
250 }
251
252 private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
253 synchronized (mCallbacks) {
254 for (BluetoothCallback callback : mCallbacks) {
255 callback.onConnectionStateChanged(cachedDevice, state);
256 }
257 }
258 }
259
260 void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
Jason Monk7ce96b92015-02-02 11:27:58 -0500261 synchronized (mCallbacks) {
262 for (BluetoothCallback callback : mCallbacks) {
263 callback.onDeviceAdded(cachedDevice);
264 }
265 }
266 }
267
268 private class DeviceDisappearedHandler implements Handler {
269 public void onReceive(Context context, Intent intent,
270 BluetoothDevice device) {
271 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
272 if (cachedDevice == null) {
273 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
274 return;
275 }
276 if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
277 synchronized (mCallbacks) {
278 for (BluetoothCallback callback : mCallbacks) {
279 callback.onDeviceDeleted(cachedDevice);
280 }
281 }
282 }
283 }
284 }
285
286 private class NameChangedHandler implements Handler {
287 public void onReceive(Context context, Intent intent,
288 BluetoothDevice device) {
289 mDeviceManager.onDeviceNameUpdated(device);
290 }
291 }
292
293 private class BondStateChangedHandler implements Handler {
294 public void onReceive(Context context, Intent intent,
295 BluetoothDevice device) {
296 if (device == null) {
297 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
298 return;
299 }
300 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
301 BluetoothDevice.ERROR);
302 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
303 if (cachedDevice == null) {
304 Log.w(TAG, "CachedBluetoothDevice for device " + device +
305 " not found, calling readPairedDevices().");
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700306 if (readPairedDevices()) {
307 cachedDevice = mDeviceManager.findDevice(device);
Jason Monk7ce96b92015-02-02 11:27:58 -0500308 }
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700309
Jason Monk7ce96b92015-02-02 11:27:58 -0500310 if (cachedDevice == null) {
Jakub Pawlowski3eb490f2016-07-18 09:18:09 -0700311 Log.w(TAG, "Got bonding state changed for " + device +
312 ", but we have no record of that device.");
313
314 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
315 dispatchDeviceAdded(cachedDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -0500316 }
317 }
318
319 synchronized (mCallbacks) {
320 for (BluetoothCallback callback : mCallbacks) {
321 callback.onDeviceBondStateChanged(cachedDevice, bondState);
322 }
323 }
324 cachedDevice.onBondingStateChanged(bondState);
325
326 if (bondState == BluetoothDevice.BOND_NONE) {
327 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
328 BluetoothDevice.ERROR);
329
330 showUnbondMessage(context, cachedDevice.getName(), reason);
331 }
332 }
333
334 /**
335 * Called when we have reached the unbonded state.
336 *
337 * @param reason one of the error reasons from
338 * BluetoothDevice.UNBOND_REASON_*
339 */
340 private void showUnbondMessage(Context context, String name, int reason) {
341 int errorMsg;
342
343 switch(reason) {
344 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
345 errorMsg = R.string.bluetooth_pairing_pin_error_message;
346 break;
347 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
348 errorMsg = R.string.bluetooth_pairing_rejected_error_message;
349 break;
350 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
351 errorMsg = R.string.bluetooth_pairing_device_down_error_message;
352 break;
353 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
354 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
355 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
356 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
357 errorMsg = R.string.bluetooth_pairing_error_message;
358 break;
359 default:
360 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
361 return;
362 }
363 Utils.showError(context, name, errorMsg);
364 }
365 }
366
367 private class ClassChangedHandler implements Handler {
368 public void onReceive(Context context, Intent intent,
369 BluetoothDevice device) {
370 mDeviceManager.onBtClassChanged(device);
371 }
372 }
373
374 private class UuidChangedHandler implements Handler {
375 public void onReceive(Context context, Intent intent,
376 BluetoothDevice device) {
377 mDeviceManager.onUuidChanged(device);
378 }
379 }
380
Jason Monk7ce96b92015-02-02 11:27:58 -0500381 private class DockEventHandler implements Handler {
382 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
383 // Remove if unpair device upon undocking
384 int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
385 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
386 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
387 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
388 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
389 if (cachedDevice != null) {
Jack He51520472017-07-24 12:30:08 -0700390 cachedDevice.setJustDiscovered(false);
Jason Monk7ce96b92015-02-02 11:27:58 -0500391 }
392 }
393 }
394 }
395 }
Jack He6258aae2017-06-29 17:01:23 -0700396
397 private class BatteryLevelChangedHandler implements Handler {
398 public void onReceive(Context context, Intent intent,
399 BluetoothDevice device) {
400 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
401 if (cachedDevice != null) {
402 cachedDevice.refresh();
403 }
404 }
405 }
406
Jason Monk7ce96b92015-02-02 11:27:58 -0500407 boolean readPairedDevices() {
408 Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
409 if (bondedDevices == null) {
410 return false;
411 }
412
413 boolean deviceAdded = false;
414 for (BluetoothDevice device : bondedDevices) {
415 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
416 if (cachedDevice == null) {
417 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
418 dispatchDeviceAdded(cachedDevice);
419 deviceAdded = true;
420 }
421 }
422
423 return deviceAdded;
424 }
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800425
426 private class ActiveDeviceChangedHandler implements Handler {
427 @Override
428 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
429 String action = intent.getAction();
430 if (action == null) {
431 Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
432 return;
433 }
434 CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
435 int bluetoothProfile = 0;
436 if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
437 bluetoothProfile = BluetoothProfile.A2DP;
438 } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
439 bluetoothProfile = BluetoothProfile.HEADSET;
Hansong Zhangd7b35912018-03-16 09:15:48 -0700440 } else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) {
441 bluetoothProfile = BluetoothProfile.HEARING_AID;
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800442 } else {
443 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
444 return;
445 }
446 dispatchActiveDeviceChanged(activeDevice, bluetoothProfile);
447 }
448 }
449
450 private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
451 int bluetoothProfile) {
Pavlin Radoslavovc285d552018-02-06 16:14:00 -0800452 mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800453 synchronized (mCallbacks) {
454 for (BluetoothCallback callback : mCallbacks) {
455 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
456 }
457 }
458 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500459}