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.settingslib.bluetooth; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothClass; 21 import android.bluetooth.BluetoothDevice; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.util.Log; 27 28 import com.android.settingslib.R; 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 public 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 { onReceive(Context context, Intent intent, BluetoothDevice device)57 void onReceive(Context context, Intent intent, BluetoothDevice device); 58 } 59 addHandler(String action, Handler handler)60 private void addHandler(String action, Handler handler) { 61 mHandlerMap.put(action, handler); 62 mAdapterIntentFilter.addAction(action); 63 } 64 addProfileHandler(String action, Handler handler)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 setProfileManager(LocalBluetoothProfileManager manager)71 void setProfileManager(LocalBluetoothProfileManager manager) { 72 mProfileManager = manager; 73 } 74 BluetoothEventManager(LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, Context context)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 registerProfileIntentReceiver()112 void registerProfileIntentReceiver() { 113 mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler); 114 } 115 setReceiverHandler(android.os.Handler handler)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. */ registerCallback(BluetoothCallback callback)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. */ unregisterCallback(BluetoothCallback callback)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 { onReceive(Context context, Intent intent, BluetoothDevice device)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 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 } 184 } 185 186 private class DeviceFoundHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)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 onReceive(Context context, Intent intent, BluetoothDevice device)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 dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state)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 dispatchDeviceAdded(CachedBluetoothDevice cachedDevice)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 { onReceive(Context context, Intent intent, BluetoothDevice device)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 { onReceive(Context context, Intent intent, BluetoothDevice device)252 public void onReceive(Context context, Intent intent, 253 BluetoothDevice device) { 254 mDeviceManager.onDeviceNameUpdated(device); 255 } 256 } 257 258 private class BondStateChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)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 */ showUnbondMessage(Context context, String name, int reason)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 { onReceive(Context context, Intent intent, BluetoothDevice device)333 public void onReceive(Context context, Intent intent, 334 BluetoothDevice device) { 335 mDeviceManager.onBtClassChanged(device); 336 } 337 } 338 339 private class UuidChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)340 public void onReceive(Context context, Intent intent, 341 BluetoothDevice device) { 342 mDeviceManager.onUuidChanged(device); 343 } 344 } 345 346 private class PairingCancelHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)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 { onReceive(Context context, Intent intent, BluetoothDevice device)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 } readPairedDevices()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