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 mDeviceManager.dispatchAudioModeChanged(); 216 for (BluetoothCallback callback : mCallbacks) { 217 callback.onAudioModeChanged(); 218 } 219 } 220 dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)221 private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, 222 int bluetoothProfile) { 223 mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile); 224 for (BluetoothCallback callback : mCallbacks) { 225 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile); 226 } 227 } 228 dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state)229 private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state) { 230 for (BluetoothCallback callback : mCallbacks) { 231 callback.onAclConnectionStateChanged(activeDevice, state); 232 } 233 } 234 235 @VisibleForTesting addHandler(String action, Handler handler)236 void addHandler(String action, Handler handler) { 237 mHandlerMap.put(action, handler); 238 mAdapterIntentFilter.addAction(action); 239 } 240 241 private class BluetoothBroadcastReceiver extends BroadcastReceiver { 242 @Override onReceive(Context context, Intent intent)243 public void onReceive(Context context, Intent intent) { 244 String action = intent.getAction(); 245 BluetoothDevice device = intent 246 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 247 248 Handler handler = mHandlerMap.get(action); 249 if (handler != null) { 250 handler.onReceive(context, intent, device); 251 } 252 } 253 } 254 255 private class AdapterStateChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)256 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 257 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 258 BluetoothAdapter.ERROR); 259 // update local profiles and get paired devices 260 mLocalAdapter.setBluetoothStateInt(state); 261 // send callback to update UI and possibly start scanning 262 for (BluetoothCallback callback : mCallbacks) { 263 callback.onBluetoothStateChanged(state); 264 } 265 // Inform CachedDeviceManager that the adapter state has changed 266 mDeviceManager.onBluetoothStateChanged(state); 267 } 268 } 269 270 private class ScanningStateChangedHandler implements Handler { 271 private final boolean mStarted; 272 ScanningStateChangedHandler(boolean started)273 ScanningStateChangedHandler(boolean started) { 274 mStarted = started; 275 } 276 onReceive(Context context, Intent intent, BluetoothDevice device)277 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 278 for (BluetoothCallback callback : mCallbacks) { 279 callback.onScanningStateChanged(mStarted); 280 } 281 mDeviceManager.onScanningStateChanged(mStarted); 282 } 283 } 284 285 private class DeviceFoundHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)286 public void onReceive(Context context, Intent intent, 287 BluetoothDevice device) { 288 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); 289 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); 290 // TODO Pick up UUID. They should be available for 2.1 devices. 291 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. 292 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 293 if (cachedDevice == null) { 294 cachedDevice = mDeviceManager.addDevice(device); 295 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " 296 + cachedDevice); 297 } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED 298 && !cachedDevice.getDevice().isConnected()) { 299 // Dispatch device add callback to show bonded but 300 // not connected devices in discovery mode 301 dispatchDeviceAdded(cachedDevice); 302 Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:" 303 + cachedDevice); 304 } else { 305 Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:" 306 + cachedDevice); 307 } 308 cachedDevice.setRssi(rssi); 309 cachedDevice.setJustDiscovered(true); 310 } 311 } 312 313 private class ConnectionStateChangedHandler implements Handler { 314 @Override onReceive(Context context, Intent intent, BluetoothDevice device)315 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 316 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 317 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 318 BluetoothAdapter.ERROR); 319 dispatchConnectionStateChanged(cachedDevice, state); 320 } 321 } 322 323 private class NameChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)324 public void onReceive(Context context, Intent intent, 325 BluetoothDevice device) { 326 mDeviceManager.onDeviceNameUpdated(device); 327 } 328 } 329 330 private class BondStateChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)331 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 332 if (device == null) { 333 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 334 return; 335 } 336 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 337 BluetoothDevice.ERROR); 338 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 339 if (cachedDevice == null) { 340 Log.w(TAG, "Got bonding state changed for " + device + 341 ", but we have no record of that device."); 342 cachedDevice = mDeviceManager.addDevice(device); 343 } 344 345 for (BluetoothCallback callback : mCallbacks) { 346 callback.onDeviceBondStateChanged(cachedDevice, bondState); 347 } 348 cachedDevice.onBondingStateChanged(bondState); 349 350 if (bondState == BluetoothDevice.BOND_NONE) { 351 /* Check if we need to remove other Hearing Aid devices */ 352 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 353 mDeviceManager.onDeviceUnpaired(cachedDevice); 354 } 355 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, 356 BluetoothDevice.ERROR); 357 358 showUnbondMessage(context, cachedDevice.getName(), reason); 359 } 360 } 361 362 /** 363 * Called when we have reached the unbonded state. 364 * 365 * @param reason one of the error reasons from 366 * BluetoothDevice.UNBOND_REASON_* 367 */ showUnbondMessage(Context context, String name, int reason)368 private void showUnbondMessage(Context context, String name, int reason) { 369 int errorMsg; 370 371 switch (reason) { 372 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: 373 errorMsg = R.string.bluetooth_pairing_pin_error_message; 374 break; 375 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: 376 errorMsg = R.string.bluetooth_pairing_rejected_error_message; 377 break; 378 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: 379 errorMsg = R.string.bluetooth_pairing_device_down_error_message; 380 break; 381 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: 382 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: 383 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: 384 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: 385 errorMsg = R.string.bluetooth_pairing_error_message; 386 break; 387 default: 388 Log.w(TAG, 389 "showUnbondMessage: Not displaying any message for reason: " + reason); 390 return; 391 } 392 BluetoothUtils.showError(context, name, errorMsg); 393 } 394 } 395 396 private class ClassChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)397 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 398 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 399 if (cachedDevice != null) { 400 cachedDevice.refresh(); 401 } 402 } 403 } 404 405 private class UuidChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)406 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 407 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 408 if (cachedDevice != null) { 409 cachedDevice.onUuidChanged(); 410 } 411 } 412 } 413 414 private class BatteryLevelChangedHandler implements Handler { onReceive(Context context, Intent intent, BluetoothDevice device)415 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 416 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 417 if (cachedDevice != null) { 418 cachedDevice.refresh(); 419 } 420 } 421 } 422 423 private class ActiveDeviceChangedHandler implements Handler { 424 @Override onReceive(Context context, Intent intent, BluetoothDevice device)425 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 426 String action = intent.getAction(); 427 if (action == null) { 428 Log.w(TAG, "ActiveDeviceChangedHandler: action is null"); 429 return; 430 } 431 CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); 432 int bluetoothProfile = 0; 433 if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { 434 bluetoothProfile = BluetoothProfile.A2DP; 435 } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { 436 bluetoothProfile = BluetoothProfile.HEADSET; 437 } else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) { 438 bluetoothProfile = BluetoothProfile.HEARING_AID; 439 } else { 440 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action); 441 return; 442 } 443 dispatchActiveDeviceChanged(activeDevice, bluetoothProfile); 444 } 445 } 446 447 private class AclStateChangedHandler implements Handler { 448 @Override onReceive(Context context, Intent intent, BluetoothDevice device)449 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 450 if (device == null) { 451 Log.w(TAG, "AclStateChangedHandler: device is null"); 452 return; 453 } 454 455 // Avoid to notify Settings UI for Hearing Aid sub device. 456 if (mDeviceManager.isSubDevice(device)) { 457 return; 458 } 459 460 final String action = intent.getAction(); 461 if (action == null) { 462 Log.w(TAG, "AclStateChangedHandler: action is null"); 463 return; 464 } 465 final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); 466 if (activeDevice == null) { 467 Log.w(TAG, "AclStateChangedHandler: activeDevice is null"); 468 return; 469 } 470 final int state; 471 switch (action) { 472 case BluetoothDevice.ACTION_ACL_CONNECTED: 473 state = BluetoothAdapter.STATE_CONNECTED; 474 break; 475 case BluetoothDevice.ACTION_ACL_DISCONNECTED: 476 state = BluetoothAdapter.STATE_DISCONNECTED; 477 break; 478 default: 479 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action); 480 return; 481 482 } 483 dispatchAclStateChanged(activeDevice, state); 484 } 485 } 486 487 private class AudioModeChangedHandler implements Handler { 488 489 @Override onReceive(Context context, Intent intent, BluetoothDevice device)490 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 491 final String action = intent.getAction(); 492 if (action == null) { 493 Log.w(TAG, "AudioModeChangedHandler() action is null"); 494 return; 495 } 496 dispatchAudioModeChanged(); 497 } 498 } 499 } 500