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