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