1 /*
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 
17 package com.android.settingslib.bluetooth;
18 
19 import android.bluetooth.BluetoothA2dp;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothClass;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadset;
24 import android.bluetooth.BluetoothHearingAid;
25 import android.bluetooth.BluetoothProfile;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.telephony.TelephonyManager;
31 import android.util.Log;
32 
33 import com.android.settingslib.R;
34 
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.HashMap;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.Set;
41 
42 /**
43  * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
44  * API and dispatches the event on the UI thread to the right class in the
45  * Settings.
46  */
47 public class BluetoothEventManager {
48     private static final String TAG = "BluetoothEventManager";
49 
50     private final LocalBluetoothAdapter mLocalAdapter;
51     private final CachedBluetoothDeviceManager mDeviceManager;
52     private LocalBluetoothProfileManager mProfileManager;
53     private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
54     private final Map<String, Handler> mHandlerMap;
55     private Context mContext;
56 
57     private final Collection<BluetoothCallback> mCallbacks =
58             new ArrayList<BluetoothCallback>();
59 
60     private android.os.Handler mReceiverHandler;
61 
62     interface Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)63         void onReceive(Context context, Intent intent, BluetoothDevice device);
64     }
65 
addHandler(String action, Handler handler)66     private void addHandler(String action, Handler handler) {
67         mHandlerMap.put(action, handler);
68         mAdapterIntentFilter.addAction(action);
69     }
70 
addProfileHandler(String action, Handler handler)71     void addProfileHandler(String action, Handler handler) {
72         mHandlerMap.put(action, handler);
73         mProfileIntentFilter.addAction(action);
74     }
75 
76     // Set profile manager after construction due to circular dependency
setProfileManager(LocalBluetoothProfileManager manager)77     void setProfileManager(LocalBluetoothProfileManager manager) {
78         mProfileManager = manager;
79     }
80 
BluetoothEventManager(LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, Context context)81     BluetoothEventManager(LocalBluetoothAdapter adapter,
82             CachedBluetoothDeviceManager deviceManager, Context context) {
83         mLocalAdapter = adapter;
84         mDeviceManager = deviceManager;
85         mAdapterIntentFilter = new IntentFilter();
86         mProfileIntentFilter = new IntentFilter();
87         mHandlerMap = new HashMap<String, Handler>();
88         mContext = context;
89 
90         // Bluetooth on/off broadcasts
91         addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
92         // Generic connected/not broadcast
93         addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
94                 new ConnectionStateChangedHandler());
95 
96         // Discovery broadcasts
97         addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
98         addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
99         addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
100         addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
101         addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
102         addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());
103 
104         // Pairing broadcasts
105         addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
106 
107         // Fine-grained state broadcasts
108         addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
109         addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
110         addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
111 
112         // Dock event broadcasts
113         addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
114 
115         // Active device broadcasts
116         addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED,
117                    new ActiveDeviceChangedHandler());
118         addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
119                    new ActiveDeviceChangedHandler());
120         addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
121                    new ActiveDeviceChangedHandler());
122 
123         // Headset state changed broadcasts
124         addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
125                 new AudioModeChangedHandler());
126         addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
127                 new AudioModeChangedHandler());
128 
129         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
130         mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
131     }
132 
registerProfileIntentReceiver()133     void registerProfileIntentReceiver() {
134         mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
135     }
136 
setReceiverHandler(android.os.Handler handler)137     public void setReceiverHandler(android.os.Handler handler) {
138         mContext.unregisterReceiver(mBroadcastReceiver);
139         mContext.unregisterReceiver(mProfileBroadcastReceiver);
140         mReceiverHandler = handler;
141         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
142         registerProfileIntentReceiver();
143     }
144 
145     /** Register to start receiving callbacks for Bluetooth events. */
registerCallback(BluetoothCallback callback)146     public void registerCallback(BluetoothCallback callback) {
147         synchronized (mCallbacks) {
148             mCallbacks.add(callback);
149         }
150     }
151 
152     /** Unregister to stop receiving callbacks for Bluetooth events. */
unregisterCallback(BluetoothCallback callback)153     public void unregisterCallback(BluetoothCallback callback) {
154         synchronized (mCallbacks) {
155             mCallbacks.remove(callback);
156         }
157     }
158 
159     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
160         @Override
161         public void onReceive(Context context, Intent intent) {
162             String action = intent.getAction();
163             BluetoothDevice device = intent
164                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
165 
166             Handler handler = mHandlerMap.get(action);
167             if (handler != null) {
168                 handler.onReceive(context, intent, device);
169             }
170         }
171     };
172 
173     private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
174         @Override
175         public void onReceive(Context context, Intent intent) {
176             String action = intent.getAction();
177             BluetoothDevice device = intent
178                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
179 
180             Handler handler = mHandlerMap.get(action);
181             if (handler != null) {
182                 handler.onReceive(context, intent, device);
183             }
184         }
185     };
186 
187     private class AdapterStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)188         public void onReceive(Context context, Intent intent,
189                 BluetoothDevice device) {
190             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
191                                     BluetoothAdapter.ERROR);
192             // Reregister Profile Broadcast Receiver as part of TURN OFF
193             if (state == BluetoothAdapter.STATE_OFF)
194             {
195                 context.unregisterReceiver(mProfileBroadcastReceiver);
196                 registerProfileIntentReceiver();
197             }
198             // update local profiles and get paired devices
199             mLocalAdapter.setBluetoothStateInt(state);
200             // send callback to update UI and possibly start scanning
201             synchronized (mCallbacks) {
202                 for (BluetoothCallback callback : mCallbacks) {
203                     callback.onBluetoothStateChanged(state);
204                 }
205             }
206             // Inform CachedDeviceManager that the adapter state has changed
207             mDeviceManager.onBluetoothStateChanged(state);
208         }
209     }
210 
211     private class ScanningStateChangedHandler implements Handler {
212         private final boolean mStarted;
213 
ScanningStateChangedHandler(boolean started)214         ScanningStateChangedHandler(boolean started) {
215             mStarted = started;
216         }
onReceive(Context context, Intent intent, BluetoothDevice device)217         public void onReceive(Context context, Intent intent,
218                 BluetoothDevice device) {
219             synchronized (mCallbacks) {
220                 for (BluetoothCallback callback : mCallbacks) {
221                     callback.onScanningStateChanged(mStarted);
222                 }
223             }
224             mDeviceManager.onScanningStateChanged(mStarted);
225         }
226     }
227 
228     private class DeviceFoundHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)229         public void onReceive(Context context, Intent intent,
230                 BluetoothDevice device) {
231             short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
232             BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
233             String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
234             // TODO Pick up UUID. They should be available for 2.1 devices.
235             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
236             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
237             if (cachedDevice == null) {
238                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
239                 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
240                         + cachedDevice);
241             }
242             cachedDevice.setRssi(rssi);
243             cachedDevice.setBtClass(btClass);
244             cachedDevice.setNewName(name);
245             cachedDevice.setJustDiscovered(true);
246         }
247     }
248 
249     private class ConnectionStateChangedHandler implements Handler {
250         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)251         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
252             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
253             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
254                     BluetoothAdapter.ERROR);
255             dispatchConnectionStateChanged(cachedDevice, state);
256         }
257     }
258 
dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state)259     private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
260         synchronized (mCallbacks) {
261             for (BluetoothCallback callback : mCallbacks) {
262                 callback.onConnectionStateChanged(cachedDevice, state);
263             }
264         }
265     }
266 
dispatchDeviceAdded(CachedBluetoothDevice cachedDevice)267     void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
268         synchronized (mCallbacks) {
269             for (BluetoothCallback callback : mCallbacks) {
270                 callback.onDeviceAdded(cachedDevice);
271             }
272         }
273     }
274 
dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice)275     void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
276         synchronized (mCallbacks) {
277             for (BluetoothCallback callback : mCallbacks) {
278                 callback.onDeviceDeleted(cachedDevice);
279             }
280         }
281     }
282 
283     private class DeviceDisappearedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)284         public void onReceive(Context context, Intent intent,
285                 BluetoothDevice device) {
286             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
287             if (cachedDevice == null) {
288                 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
289                 return;
290             }
291             if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
292                 synchronized (mCallbacks) {
293                     for (BluetoothCallback callback : mCallbacks) {
294                         callback.onDeviceDeleted(cachedDevice);
295                     }
296                 }
297             }
298         }
299     }
300 
301     private class NameChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)302         public void onReceive(Context context, Intent intent,
303                 BluetoothDevice device) {
304             mDeviceManager.onDeviceNameUpdated(device);
305         }
306     }
307 
308     private class BondStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)309         public void onReceive(Context context, Intent intent,
310                 BluetoothDevice device) {
311             if (device == null) {
312                 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
313                 return;
314             }
315             int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
316                                                BluetoothDevice.ERROR);
317             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
318             if (cachedDevice == null) {
319                 Log.w(TAG, "CachedBluetoothDevice for device " + device +
320                         " not found, calling readPairedDevices().");
321                 if (readPairedDevices()) {
322                     cachedDevice = mDeviceManager.findDevice(device);
323                 }
324 
325                 if (cachedDevice == null) {
326                     Log.w(TAG, "Got bonding state changed for " + device +
327                             ", but we have no record of that device.");
328 
329                     cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
330                     dispatchDeviceAdded(cachedDevice);
331                 }
332             }
333 
334             synchronized (mCallbacks) {
335                 for (BluetoothCallback callback : mCallbacks) {
336                     callback.onDeviceBondStateChanged(cachedDevice, bondState);
337                 }
338             }
339             cachedDevice.onBondingStateChanged(bondState);
340 
341             if (bondState == BluetoothDevice.BOND_NONE) {
342                 /* Check if we need to remove other Hearing Aid devices */
343                 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
344                     mDeviceManager.onDeviceUnpaired(cachedDevice);
345                 }
346                 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
347                         BluetoothDevice.ERROR);
348 
349                 showUnbondMessage(context, cachedDevice.getName(), reason);
350             }
351         }
352 
353         /**
354          * Called when we have reached the unbonded state.
355          *
356          * @param reason one of the error reasons from
357          *            BluetoothDevice.UNBOND_REASON_*
358          */
showUnbondMessage(Context context, String name, int reason)359         private void showUnbondMessage(Context context, String name, int reason) {
360             int errorMsg;
361 
362             switch(reason) {
363             case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
364                 errorMsg = R.string.bluetooth_pairing_pin_error_message;
365                 break;
366             case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
367                 errorMsg = R.string.bluetooth_pairing_rejected_error_message;
368                 break;
369             case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
370                 errorMsg = R.string.bluetooth_pairing_device_down_error_message;
371                 break;
372             case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
373             case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
374             case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
375             case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
376                 errorMsg = R.string.bluetooth_pairing_error_message;
377                 break;
378             default:
379                 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
380                 return;
381             }
382             Utils.showError(context, name, errorMsg);
383         }
384     }
385 
386     private class ClassChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)387         public void onReceive(Context context, Intent intent,
388                 BluetoothDevice device) {
389             mDeviceManager.onBtClassChanged(device);
390         }
391     }
392 
393     private class UuidChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)394         public void onReceive(Context context, Intent intent,
395                 BluetoothDevice device) {
396             mDeviceManager.onUuidChanged(device);
397         }
398     }
399 
400     private class DockEventHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)401         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
402             // Remove if unpair device upon undocking
403             int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
404             int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
405             if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
406                 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
407                     CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
408                     if (cachedDevice != null) {
409                         cachedDevice.setJustDiscovered(false);
410                     }
411                 }
412             }
413         }
414     }
415 
416     private class BatteryLevelChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)417         public void onReceive(Context context, Intent intent,
418                 BluetoothDevice device) {
419             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
420             if (cachedDevice != null) {
421                 cachedDevice.refresh();
422             }
423         }
424     }
425 
readPairedDevices()426     boolean readPairedDevices() {
427         Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
428         if (bondedDevices == null) {
429             return false;
430         }
431 
432         boolean deviceAdded = false;
433         for (BluetoothDevice device : bondedDevices) {
434             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
435             if (cachedDevice == null) {
436                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
437                 dispatchDeviceAdded(cachedDevice);
438                 deviceAdded = true;
439             }
440         }
441 
442         return deviceAdded;
443     }
444 
445     private class ActiveDeviceChangedHandler implements Handler {
446         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)447         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
448             String action = intent.getAction();
449             if (action == null) {
450                 Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
451                 return;
452             }
453             CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
454             int bluetoothProfile = 0;
455             if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
456                 bluetoothProfile = BluetoothProfile.A2DP;
457             } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
458                 bluetoothProfile = BluetoothProfile.HEADSET;
459             } else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) {
460                 bluetoothProfile = BluetoothProfile.HEARING_AID;
461             } else {
462                 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
463                 return;
464             }
465             dispatchActiveDeviceChanged(activeDevice, bluetoothProfile);
466         }
467     }
468 
dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)469     private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
470                                              int bluetoothProfile) {
471         mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
472         synchronized (mCallbacks) {
473             for (BluetoothCallback callback : mCallbacks) {
474                 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
475             }
476         }
477     }
478 
479     private class AudioModeChangedHandler implements Handler {
480 
481         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)482         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
483             final String action = intent.getAction();
484             if (action == null) {
485                 Log.w(TAG, "AudioModeChangedHandler() action is null");
486                 return;
487             }
488             dispatchAudioModeChanged();
489         }
490     }
491 
dispatchAudioModeChanged()492     private void dispatchAudioModeChanged() {
493         mDeviceManager.dispatchAudioModeChanged();
494         synchronized (mCallbacks) {
495             for (BluetoothCallback callback : mCallbacks) {
496                 callback.onAudioModeChanged();
497             }
498         }
499     }
500 
dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, int bluetoothProfile)501     void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state,
502             int bluetoothProfile) {
503         synchronized (mCallbacks) {
504             for (BluetoothCallback callback : mCallbacks) {
505                 callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
506             }
507         }
508         mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile);
509     }
510 }
511