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.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothClass; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadset; 24 import android.bluetooth.BluetoothHearingAid; 25 import android.bluetooth.BluetoothProfile; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.telephony.TelephonyManager; 31 import android.util.Log; 32 33 import com.android.settingslib.R; 34 35 import java.util.ArrayList; 36 import java.util.Collection; 37 import java.util.HashMap; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.Set; 41 42 /** 43 * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth 44 * API and dispatches the event on the UI thread to the right class in the 45 * Settings. 46 */ 47 public class BluetoothEventManager { 48 private static final String TAG = "BluetoothEventManager"; 49 50 private final LocalBluetoothAdapter mLocalAdapter; 51 private final CachedBluetoothDeviceManager mDeviceManager; 52 private LocalBluetoothProfileManager mProfileManager; 53 private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter; 54 private final Map<String, Handler> mHandlerMap; 55 private Context mContext; 56 57 private final Collection<BluetoothCallback> mCallbacks = 58 new ArrayList<BluetoothCallback>(); 59 60 private android.os.Handler mReceiverHandler; 61 62 interface Handler { onReceive(Context context, Intent intent, BluetoothDevice device)63 void onReceive(Context context, Intent intent, BluetoothDevice device); 64 } 65 addHandler(String action, Handler handler)66 private void addHandler(String action, Handler handler) { 67 mHandlerMap.put(action, handler); 68 mAdapterIntentFilter.addAction(action); 69 } 70 addProfileHandler(String action, Handler handler)71 void addProfileHandler(String action, Handler handler) { 72 mHandlerMap.put(action, handler); 73 mProfileIntentFilter.addAction(action); 74 } 75 76 // Set profile manager after construction due to circular dependency setProfileManager(LocalBluetoothProfileManager manager)77 void setProfileManager(LocalBluetoothProfileManager manager) { 78 mProfileManager = manager; 79 } 80 BluetoothEventManager(LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, Context context)81 BluetoothEventManager(LocalBluetoothAdapter adapter, 82 CachedBluetoothDeviceManager deviceManager, Context context) { 83 mLocalAdapter = adapter; 84 mDeviceManager = deviceManager; 85 mAdapterIntentFilter = new IntentFilter(); 86 mProfileIntentFilter = new IntentFilter(); 87 mHandlerMap = new HashMap<String, Handler>(); 88 mContext = context; 89 90 // Bluetooth on/off broadcasts 91 addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler()); 92 // Generic connected/not broadcast 93 addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED, 94 new ConnectionStateChangedHandler()); 95 96 // Discovery broadcasts 97 addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); 98 addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); 99 addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); 100 addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); 101 addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); 102 addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler()); 103 104 // Pairing broadcasts 105 addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler()); 106 107 // Fine-grained state broadcasts 108 addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler()); 109 addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); 110 addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler()); 111 112 // Dock event broadcasts 113 addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); 114 115 // Active device broadcasts 116 addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, 117 new ActiveDeviceChangedHandler()); 118 addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, 119 new ActiveDeviceChangedHandler()); 120 addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED, 121 new ActiveDeviceChangedHandler()); 122 123 // Headset state changed broadcasts 124 addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED, 125 new AudioModeChangedHandler()); 126 addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED, 127 new AudioModeChangedHandler()); 128 129 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler); 130 mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler); 131 } 132 registerProfileIntentReceiver()133 void registerProfileIntentReceiver() { 134 mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler); 135 } 136 setReceiverHandler(android.os.Handler handler)137 public void setReceiverHandler(android.os.Handler handler) { 138 mContext.unregisterReceiver(mBroadcastReceiver); 139 mContext.unregisterReceiver(mProfileBroadcastReceiver); 140 mReceiverHandler = handler; 141 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler); 142 registerProfileIntentReceiver(); 143 } 144 145 /** Register to start receiving callbacks for Bluetooth events. */ registerCallback(BluetoothCallback callback)146 public void registerCallback(BluetoothCallback callback) { 147 synchronized (mCallbacks) { 148 mCallbacks.add(callback); 149 } 150 } 151 152 /** Unregister to stop receiving callbacks for Bluetooth events. */ unregisterCallback(BluetoothCallback callback)153 public void unregisterCallback(BluetoothCallback callback) { 154 synchronized (mCallbacks) { 155 mCallbacks.remove(callback); 156 } 157 } 158 159 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 160 @Override 161 public void onReceive(Context context, Intent intent) { 162 String action = intent.getAction(); 163 BluetoothDevice device = intent 164 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 165 166 Handler handler = mHandlerMap.get(action); 167 if (handler != null) { 168 handler.onReceive(context, intent, device); 169 } 170 } 171 }; 172 173 private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() { 174 @Override 175 public void onReceive(Context context, Intent intent) { 176 String action = intent.getAction(); 177 BluetoothDevice device = intent 178 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 179 180 Handler handler = mHandlerMap.get(action); 181 if (handler != null) { 182 handler.onReceive(context, intent, device); 183 } 184 } 185 }; 186 187 private class AdapterStateChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)188 public void onReceive(Context context, Intent intent, 189 BluetoothDevice device) { 190 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 191 BluetoothAdapter.ERROR); 192 // Reregister Profile Broadcast Receiver as part of TURN OFF 193 if (state == BluetoothAdapter.STATE_OFF) 194 { 195 context.unregisterReceiver(mProfileBroadcastReceiver); 196 registerProfileIntentReceiver(); 197 } 198 // update local profiles and get paired devices 199 mLocalAdapter.setBluetoothStateInt(state); 200 // send callback to update UI and possibly start scanning 201 synchronized (mCallbacks) { 202 for (BluetoothCallback callback : mCallbacks) { 203 callback.onBluetoothStateChanged(state); 204 } 205 } 206 // Inform CachedDeviceManager that the adapter state has changed 207 mDeviceManager.onBluetoothStateChanged(state); 208 } 209 } 210 211 private class ScanningStateChangedHandler implements Handler { 212 private final boolean mStarted; 213 ScanningStateChangedHandler(boolean started)214 ScanningStateChangedHandler(boolean started) { 215 mStarted = started; 216 } onReceive(Context context, Intent intent, BluetoothDevice device)217 public void onReceive(Context context, Intent intent, 218 BluetoothDevice device) { 219 synchronized (mCallbacks) { 220 for (BluetoothCallback callback : mCallbacks) { 221 callback.onScanningStateChanged(mStarted); 222 } 223 } 224 mDeviceManager.onScanningStateChanged(mStarted); 225 } 226 } 227 228 private class DeviceFoundHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)229 public void onReceive(Context context, Intent intent, 230 BluetoothDevice device) { 231 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); 232 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); 233 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); 234 // TODO Pick up UUID. They should be available for 2.1 devices. 235 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. 236 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 237 if (cachedDevice == null) { 238 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 239 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " 240 + cachedDevice); 241 } 242 cachedDevice.setRssi(rssi); 243 cachedDevice.setBtClass(btClass); 244 cachedDevice.setNewName(name); 245 cachedDevice.setJustDiscovered(true); 246 } 247 } 248 249 private class ConnectionStateChangedHandler implements Handler { 250 @Override onReceive(Context context, Intent intent, BluetoothDevice device)251 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 252 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 253 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 254 BluetoothAdapter.ERROR); 255 dispatchConnectionStateChanged(cachedDevice, state); 256 } 257 } 258 dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state)259 private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { 260 synchronized (mCallbacks) { 261 for (BluetoothCallback callback : mCallbacks) { 262 callback.onConnectionStateChanged(cachedDevice, state); 263 } 264 } 265 } 266 dispatchDeviceAdded(CachedBluetoothDevice cachedDevice)267 void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { 268 synchronized (mCallbacks) { 269 for (BluetoothCallback callback : mCallbacks) { 270 callback.onDeviceAdded(cachedDevice); 271 } 272 } 273 } 274 dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice)275 void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) { 276 synchronized (mCallbacks) { 277 for (BluetoothCallback callback : mCallbacks) { 278 callback.onDeviceDeleted(cachedDevice); 279 } 280 } 281 } 282 283 private class DeviceDisappearedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)284 public void onReceive(Context context, Intent intent, 285 BluetoothDevice device) { 286 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 287 if (cachedDevice == null) { 288 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); 289 return; 290 } 291 if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { 292 synchronized (mCallbacks) { 293 for (BluetoothCallback callback : mCallbacks) { 294 callback.onDeviceDeleted(cachedDevice); 295 } 296 } 297 } 298 } 299 } 300 301 private class NameChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)302 public void onReceive(Context context, Intent intent, 303 BluetoothDevice device) { 304 mDeviceManager.onDeviceNameUpdated(device); 305 } 306 } 307 308 private class BondStateChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)309 public void onReceive(Context context, Intent intent, 310 BluetoothDevice device) { 311 if (device == null) { 312 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 313 return; 314 } 315 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 316 BluetoothDevice.ERROR); 317 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 318 if (cachedDevice == null) { 319 Log.w(TAG, "CachedBluetoothDevice for device " + device + 320 " not found, calling readPairedDevices()."); 321 if (readPairedDevices()) { 322 cachedDevice = mDeviceManager.findDevice(device); 323 } 324 325 if (cachedDevice == null) { 326 Log.w(TAG, "Got bonding state changed for " + device + 327 ", but we have no record of that device."); 328 329 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 330 dispatchDeviceAdded(cachedDevice); 331 } 332 } 333 334 synchronized (mCallbacks) { 335 for (BluetoothCallback callback : mCallbacks) { 336 callback.onDeviceBondStateChanged(cachedDevice, bondState); 337 } 338 } 339 cachedDevice.onBondingStateChanged(bondState); 340 341 if (bondState == BluetoothDevice.BOND_NONE) { 342 /* Check if we need to remove other Hearing Aid devices */ 343 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 344 mDeviceManager.onDeviceUnpaired(cachedDevice); 345 } 346 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, 347 BluetoothDevice.ERROR); 348 349 showUnbondMessage(context, cachedDevice.getName(), reason); 350 } 351 } 352 353 /** 354 * Called when we have reached the unbonded state. 355 * 356 * @param reason one of the error reasons from 357 * BluetoothDevice.UNBOND_REASON_* 358 */ showUnbondMessage(Context context, String name, int reason)359 private void showUnbondMessage(Context context, String name, int reason) { 360 int errorMsg; 361 362 switch(reason) { 363 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: 364 errorMsg = R.string.bluetooth_pairing_pin_error_message; 365 break; 366 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: 367 errorMsg = R.string.bluetooth_pairing_rejected_error_message; 368 break; 369 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: 370 errorMsg = R.string.bluetooth_pairing_device_down_error_message; 371 break; 372 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: 373 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: 374 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: 375 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: 376 errorMsg = R.string.bluetooth_pairing_error_message; 377 break; 378 default: 379 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); 380 return; 381 } 382 Utils.showError(context, name, errorMsg); 383 } 384 } 385 386 private class ClassChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)387 public void onReceive(Context context, Intent intent, 388 BluetoothDevice device) { 389 mDeviceManager.onBtClassChanged(device); 390 } 391 } 392 393 private class UuidChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)394 public void onReceive(Context context, Intent intent, 395 BluetoothDevice device) { 396 mDeviceManager.onUuidChanged(device); 397 } 398 } 399 400 private class DockEventHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)401 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 402 // Remove if unpair device upon undocking 403 int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; 404 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); 405 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { 406 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { 407 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 408 if (cachedDevice != null) { 409 cachedDevice.setJustDiscovered(false); 410 } 411 } 412 } 413 } 414 } 415 416 private class BatteryLevelChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)417 public void onReceive(Context context, Intent intent, 418 BluetoothDevice device) { 419 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 420 if (cachedDevice != null) { 421 cachedDevice.refresh(); 422 } 423 } 424 } 425 readPairedDevices()426 boolean readPairedDevices() { 427 Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices(); 428 if (bondedDevices == null) { 429 return false; 430 } 431 432 boolean deviceAdded = false; 433 for (BluetoothDevice device : bondedDevices) { 434 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 435 if (cachedDevice == null) { 436 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 437 dispatchDeviceAdded(cachedDevice); 438 deviceAdded = true; 439 } 440 } 441 442 return deviceAdded; 443 } 444 445 private class ActiveDeviceChangedHandler implements Handler { 446 @Override onReceive(Context context, Intent intent, BluetoothDevice device)447 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 448 String action = intent.getAction(); 449 if (action == null) { 450 Log.w(TAG, "ActiveDeviceChangedHandler: action is null"); 451 return; 452 } 453 CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); 454 int bluetoothProfile = 0; 455 if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { 456 bluetoothProfile = BluetoothProfile.A2DP; 457 } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { 458 bluetoothProfile = BluetoothProfile.HEADSET; 459 } else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) { 460 bluetoothProfile = BluetoothProfile.HEARING_AID; 461 } else { 462 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action); 463 return; 464 } 465 dispatchActiveDeviceChanged(activeDevice, bluetoothProfile); 466 } 467 } 468 dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)469 private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, 470 int bluetoothProfile) { 471 mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile); 472 synchronized (mCallbacks) { 473 for (BluetoothCallback callback : mCallbacks) { 474 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile); 475 } 476 } 477 } 478 479 private class AudioModeChangedHandler implements Handler { 480 481 @Override onReceive(Context context, Intent intent, BluetoothDevice device)482 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 483 final String action = intent.getAction(); 484 if (action == null) { 485 Log.w(TAG, "AudioModeChangedHandler() action is null"); 486 return; 487 } 488 dispatchAudioModeChanged(); 489 } 490 } 491 dispatchAudioModeChanged()492 private void dispatchAudioModeChanged() { 493 mDeviceManager.dispatchAudioModeChanged(); 494 synchronized (mCallbacks) { 495 for (BluetoothCallback callback : mCallbacks) { 496 callback.onAudioModeChanged(); 497 } 498 } 499 } 500 dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, int bluetoothProfile)501 void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, 502 int bluetoothProfile) { 503 synchronized (mCallbacks) { 504 for (BluetoothCallback callback : mCallbacks) { 505 callback.onProfileConnectionStateChanged(device, state, bluetoothProfile); 506 } 507 } 508 mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile); 509 } 510 } 511