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.BluetoothAdapter;
20 import android.bluetooth.BluetoothClass;
21 import android.bluetooth.BluetoothDevice;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.util.Log;
27 
28 import com.android.settingslib.R;
29 
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.Map;
34 import 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  */
41 public 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 
54     private android.os.Handler mReceiverHandler;
55 
56     interface Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)57         void onReceive(Context context, Intent intent, BluetoothDevice device);
58     }
59 
addHandler(String action, Handler handler)60     private void addHandler(String action, Handler handler) {
61         mHandlerMap.put(action, handler);
62         mAdapterIntentFilter.addAction(action);
63     }
64 
addProfileHandler(String action, Handler handler)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
setProfileManager(LocalBluetoothProfileManager manager)71     void setProfileManager(LocalBluetoothProfileManager manager) {
72         mProfileManager = manager;
73     }
74 
BluetoothEventManager(LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, Context context)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());
86         // Generic connected/not broadcast
87         addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
88                 new ConnectionStateChangedHandler());
89 
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());
96         addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());
97 
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 
109         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
110     }
111 
registerProfileIntentReceiver()112     void registerProfileIntentReceiver() {
113         mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
114     }
115 
setReceiverHandler(android.os.Handler handler)116     public void setReceiverHandler(android.os.Handler handler) {
117         mContext.unregisterReceiver(mBroadcastReceiver);
118         mReceiverHandler = handler;
119         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
120         registerProfileIntentReceiver();
121     }
122 
123     /** Register to start receiving callbacks for Bluetooth events. */
registerCallback(BluetoothCallback callback)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. */
unregisterCallback(BluetoothCallback callback)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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 
ScanningStateChangedHandler(boolean started)172         ScanningStateChangedHandler(boolean started) {
173             mStarted = started;
174         }
onReceive(Context context, Intent intent, BluetoothDevice device)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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);
199             }
200             cachedDevice.setRssi(rssi);
201             cachedDevice.setBtClass(btClass);
202             cachedDevice.setNewName(name);
203             cachedDevice.setVisible(true);
204         }
205     }
206 
207     private class ConnectionStateChangedHandler implements Handler {
208         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)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 
dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state)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 
dispatchDeviceAdded(CachedBluetoothDevice cachedDevice)225     void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
226         synchronized (mCallbacks) {
227             for (BluetoothCallback callback : mCallbacks) {
228                 callback.onDeviceAdded(cachedDevice);
229             }
230         }
231     }
232 
233     private class DeviceDisappearedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)252         public void onReceive(Context context, Intent intent,
253                 BluetoothDevice device) {
254             mDeviceManager.onDeviceNameUpdated(device);
255         }
256     }
257 
258     private class BondStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)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().");
271                 if (!readPairedDevices()) {
272                     Log.e(TAG, "Got bonding state changed for " + device +
273                             ", but we have no record of that device.");
274                     return;
275                 }
276                 cachedDevice = mDeviceManager.findDevice(device);
277                 if (cachedDevice == null) {
278                     Log.e(TAG, "Got bonding state changed for " + device +
279                             ", but device not added in cache.");
280                     return;
281                 }
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          */
showUnbondMessage(Context context, String name, int reason)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)333         public void onReceive(Context context, Intent intent,
334                 BluetoothDevice device) {
335             mDeviceManager.onBtClassChanged(device);
336         }
337     }
338 
339     private class UuidChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)340         public void onReceive(Context context, Intent intent,
341                 BluetoothDevice device) {
342             mDeviceManager.onUuidChanged(device);
343         }
344     }
345 
346     private class PairingCancelHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)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             }
352             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
353             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;
358             if (context != null && cachedDevice != null) {
359                 Utils.showError(context, cachedDevice.getName(), errorMsg);
360             }
361         }
362     }
363 
364     private class DockEventHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)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     }
readPairedDevices()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 }
398