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.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothClass;
21import android.bluetooth.BluetoothDevice;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.util.Log;
27
28import com.android.settingslib.R;
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 */
41public 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 {
57        void onReceive(Context context, Intent intent, BluetoothDevice device);
58    }
59
60    private void addHandler(String action, Handler handler) {
61        mHandlerMap.put(action, handler);
62        mAdapterIntentFilter.addAction(action);
63    }
64
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
71    void setProfileManager(LocalBluetoothProfileManager manager) {
72        mProfileManager = manager;
73    }
74
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
112    void registerProfileIntentReceiver() {
113        mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
114    }
115
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. */
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. */
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 {
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
172        ScanningStateChangedHandler(boolean started) {
173            mStarted = started;
174        }
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 {
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
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
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
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 {
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 {
252        public void onReceive(Context context, Intent intent,
253                BluetoothDevice device) {
254            mDeviceManager.onDeviceNameUpdated(device);
255        }
256    }
257
258    private class BondStateChangedHandler implements Handler {
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         */
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 {
333        public void onReceive(Context context, Intent intent,
334                BluetoothDevice device) {
335            mDeviceManager.onBtClassChanged(device);
336        }
337    }
338
339    private class UuidChangedHandler implements Handler {
340        public void onReceive(Context context, Intent intent,
341                BluetoothDevice device) {
342            mDeviceManager.onUuidChanged(device);
343        }
344    }
345
346    private class PairingCancelHandler implements Handler {
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 {
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    }
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