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