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