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