• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.settings.bluetooth;
18 
19 import com.android.settings.R;
20 
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothClass;
23 import android.bluetooth.BluetoothDevice;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.util.Log;
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 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     interface Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)55         void onReceive(Context context, Intent intent, BluetoothDevice device);
56     }
57 
addHandler(String action, Handler handler)58     void addHandler(String action, Handler handler) {
59         mHandlerMap.put(action, handler);
60         mAdapterIntentFilter.addAction(action);
61     }
62 
addProfileHandler(String action, Handler handler)63     void addProfileHandler(String action, Handler handler) {
64         mHandlerMap.put(action, handler);
65         mProfileIntentFilter.addAction(action);
66     }
67 
68     // Set profile manager after construction due to circular dependency
setProfileManager(LocalBluetoothProfileManager manager)69     void setProfileManager(LocalBluetoothProfileManager manager) {
70         mProfileManager = manager;
71     }
72 
BluetoothEventManager(LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, Context context)73     BluetoothEventManager(LocalBluetoothAdapter adapter,
74             CachedBluetoothDeviceManager deviceManager, Context context) {
75         mLocalAdapter = adapter;
76         mDeviceManager = deviceManager;
77         mAdapterIntentFilter = new IntentFilter();
78         mProfileIntentFilter = new IntentFilter();
79         mHandlerMap = new HashMap<String, Handler>();
80         mContext = context;
81 
82         // Bluetooth on/off broadcasts
83         addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
84 
85         // Discovery broadcasts
86         addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
87         addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
88         addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
89         addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
90         addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
91 
92         // Pairing broadcasts
93         addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
94         addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
95 
96         // Fine-grained state broadcasts
97         addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
98         addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
99 
100         // Dock event broadcasts
101         addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
102         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
103     }
104 
registerProfileIntentReceiver()105     void registerProfileIntentReceiver() {
106         mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter);
107     }
108 
109     /** Register to start receiving callbacks for Bluetooth events. */
registerCallback(BluetoothCallback callback)110     void registerCallback(BluetoothCallback callback) {
111         synchronized (mCallbacks) {
112             mCallbacks.add(callback);
113         }
114     }
115 
116     /** Unregister to stop receiving callbacks for Bluetooth events. */
unregisterCallback(BluetoothCallback callback)117     void unregisterCallback(BluetoothCallback callback) {
118         synchronized (mCallbacks) {
119             mCallbacks.remove(callback);
120         }
121     }
122 
123     // This can't be called from a broadcast receiver where the filter is set in the Manifest.
getDockedDeviceAddress(Context context)124     private static String getDockedDeviceAddress(Context context) {
125         // This works only because these broadcast intents are "sticky"
126         Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
127         if (i != null) {
128             int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
129             if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
130                 BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
131                 if (device != null) {
132                     return device.getAddress();
133                 }
134             }
135         }
136         return null;
137     }
138 
139     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
140         @Override
141         public void onReceive(Context context, Intent intent) {
142             String action = intent.getAction();
143             BluetoothDevice device = intent
144                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
145 
146             Handler handler = mHandlerMap.get(action);
147             if (handler != null) {
148                 handler.onReceive(context, intent, device);
149             }
150         }
151     };
152 
153     private class AdapterStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)154         public void onReceive(Context context, Intent intent,
155                 BluetoothDevice device) {
156             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
157                                     BluetoothAdapter.ERROR);
158             // update local profiles and get paired devices
159             mLocalAdapter.setBluetoothStateInt(state);
160             // send callback to update UI and possibly start scanning
161             synchronized (mCallbacks) {
162                 for (BluetoothCallback callback : mCallbacks) {
163                     callback.onBluetoothStateChanged(state);
164                 }
165             }
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             LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
184         }
185     }
186 
187     private class DeviceFoundHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)188         public void onReceive(Context context, Intent intent,
189                 BluetoothDevice device) {
190             short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
191             BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
192             String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
193             // TODO Pick up UUID. They should be available for 2.1 devices.
194             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
195             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
196             if (cachedDevice == null) {
197                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
198                 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
199                         + cachedDevice);
200                 // callback to UI to create Preference for new device
201                 dispatchDeviceAdded(cachedDevice);
202             }
203             cachedDevice.setRssi(rssi);
204             cachedDevice.setBtClass(btClass);
205             cachedDevice.setName(name);
206             cachedDevice.setVisible(true);
207         }
208     }
209 
dispatchDeviceAdded(CachedBluetoothDevice cachedDevice)210     private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
211         synchronized (mCallbacks) {
212             for (BluetoothCallback callback : mCallbacks) {
213                 callback.onDeviceAdded(cachedDevice);
214             }
215         }
216     }
217 
218     private class DeviceDisappearedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)219         public void onReceive(Context context, Intent intent,
220                 BluetoothDevice device) {
221             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
222             if (cachedDevice == null) {
223                 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
224                 return;
225             }
226             if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
227                 synchronized (mCallbacks) {
228                     for (BluetoothCallback callback : mCallbacks) {
229                         callback.onDeviceDeleted(cachedDevice);
230                     }
231                 }
232             }
233         }
234     }
235 
236     private class NameChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)237         public void onReceive(Context context, Intent intent,
238                 BluetoothDevice device) {
239             mDeviceManager.onDeviceNameUpdated(device);
240         }
241     }
242 
243     private class BondStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)244         public void onReceive(Context context, Intent intent,
245                 BluetoothDevice device) {
246             if (device == null) {
247                 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
248                 return;
249             }
250             int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
251                                                BluetoothDevice.ERROR);
252             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
253             if (cachedDevice == null) {
254                 Log.w(TAG, "CachedBluetoothDevice for device " + device +
255                         " not found, calling readPairedDevices().");
256                 if (!readPairedDevices()) {
257                     Log.e(TAG, "Got bonding state changed for " + device +
258                             ", but we have no record of that device.");
259                     return;
260                 }
261                 cachedDevice = mDeviceManager.findDevice(device);
262                 if (cachedDevice == null) {
263                     Log.e(TAG, "Got bonding state changed for " + device +
264                             ", but device not added in cache.");
265                     return;
266                 }
267             }
268 
269             synchronized (mCallbacks) {
270                 for (BluetoothCallback callback : mCallbacks) {
271                     callback.onDeviceBondStateChanged(cachedDevice, bondState);
272                 }
273             }
274             cachedDevice.onBondingStateChanged(bondState);
275 
276             if (bondState == BluetoothDevice.BOND_NONE) {
277                 if (device.isBluetoothDock()) {
278                     // After a dock is unpaired, we will forget the settings
279                     LocalBluetoothPreferences
280                             .removeDockAutoConnectSetting(context, device.getAddress());
281 
282                     // if the device is undocked, remove it from the list as well
283                     if (!device.getAddress().equals(getDockedDeviceAddress(context))) {
284                         cachedDevice.setVisible(false);
285                     }
286                 }
287                 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
288                         BluetoothDevice.ERROR);
289 
290                 showUnbondMessage(context, cachedDevice.getName(), reason);
291             }
292         }
293 
294         /**
295          * Called when we have reached the unbonded state.
296          *
297          * @param reason one of the error reasons from
298          *            BluetoothDevice.UNBOND_REASON_*
299          */
showUnbondMessage(Context context, String name, int reason)300         private void showUnbondMessage(Context context, String name, int reason) {
301             int errorMsg;
302 
303             switch(reason) {
304             case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
305                 errorMsg = R.string.bluetooth_pairing_pin_error_message;
306                 break;
307             case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
308                 errorMsg = R.string.bluetooth_pairing_rejected_error_message;
309                 break;
310             case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
311                 errorMsg = R.string.bluetooth_pairing_device_down_error_message;
312                 break;
313             case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
314             case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
315             case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
316             case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
317                 errorMsg = R.string.bluetooth_pairing_error_message;
318                 break;
319             default:
320                 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
321                 return;
322             }
323             Utils.showError(context, name, errorMsg);
324         }
325     }
326 
327     private class ClassChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)328         public void onReceive(Context context, Intent intent,
329                 BluetoothDevice device) {
330             mDeviceManager.onBtClassChanged(device);
331         }
332     }
333 
334     private class UuidChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)335         public void onReceive(Context context, Intent intent,
336                 BluetoothDevice device) {
337             mDeviceManager.onUuidChanged(device);
338         }
339     }
340 
341     private class PairingCancelHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)342         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
343             if (device == null) {
344                 Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
345                 return;
346             }
347             int errorMsg = R.string.bluetooth_pairing_error_message;
348             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
349             Utils.showError(context, cachedDevice.getName(), errorMsg);
350         }
351     }
352 
353     private class DockEventHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)354         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
355             // Remove if unpair device upon undocking
356             int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
357             int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
358             if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
359                 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
360                     CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
361                     if (cachedDevice != null) {
362                         cachedDevice.setVisible(false);
363                     }
364                 }
365             }
366         }
367     }
368 
readPairedDevices()369     boolean readPairedDevices() {
370         Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
371         if (bondedDevices == null) {
372             return false;
373         }
374 
375         boolean deviceAdded = false;
376         for (BluetoothDevice device : bondedDevices) {
377             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
378             if (cachedDevice == null) {
379                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
380                 dispatchDeviceAdded(cachedDevice);
381                 deviceAdded = true;
382             }
383         }
384 
385         return deviceAdded;
386     }
387 }
388