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