1 /* 2 * Copyright 2020 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.server.media; 18 19 import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; 20 import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED; 21 import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.bluetooth.BluetoothA2dp; 26 import android.bluetooth.BluetoothAdapter; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.BluetoothHearingAid; 29 import android.bluetooth.BluetoothLeAudio; 30 import android.bluetooth.BluetoothProfile; 31 import android.content.BroadcastReceiver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.media.AudioManager; 36 import android.media.AudioSystem; 37 import android.media.MediaRoute2Info; 38 import android.os.UserHandle; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.util.Slog; 42 import android.util.SparseBooleanArray; 43 import android.util.SparseIntArray; 44 45 import com.android.internal.R; 46 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.Iterator; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Objects; 53 import java.util.Set; 54 55 class BluetoothRouteProvider { 56 private static final String TAG = "BTRouteProvider"; 57 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 58 59 private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; 60 private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_"; 61 62 @SuppressWarnings("WeakerAccess") /* synthetic access */ 63 // Maps hardware address to BluetoothRouteInfo 64 final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); 65 @SuppressWarnings("WeakerAccess") /* synthetic access */ 66 final List<BluetoothRouteInfo> mActiveRoutes = new ArrayList<>(); 67 @SuppressWarnings("WeakerAccess") /* synthetic access */ 68 BluetoothA2dp mA2dpProfile; 69 @SuppressWarnings("WeakerAccess") /* synthetic access */ 70 BluetoothHearingAid mHearingAidProfile; 71 @SuppressWarnings("WeakerAccess") /* synthetic access */ 72 BluetoothLeAudio mLeAudioProfile; 73 74 // Route type -> volume map 75 private final SparseIntArray mVolumeMap = new SparseIntArray(); 76 77 private final Context mContext; 78 private final BluetoothAdapter mBluetoothAdapter; 79 private final BluetoothRoutesUpdatedListener mListener; 80 private final AudioManager mAudioManager; 81 private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>(); 82 private final IntentFilter mIntentFilter = new IntentFilter(); 83 private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver(); 84 private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener(); 85 86 /** 87 * Create an instance of {@link BluetoothRouteProvider}. 88 * It may return {@code null} if Bluetooth is not supported on this hardware platform. 89 */ 90 @Nullable createInstance(@onNull Context context, @NonNull BluetoothRoutesUpdatedListener listener)91 static BluetoothRouteProvider createInstance(@NonNull Context context, 92 @NonNull BluetoothRoutesUpdatedListener listener) { 93 Objects.requireNonNull(context); 94 Objects.requireNonNull(listener); 95 96 BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); 97 if (btAdapter == null) { 98 return null; 99 } 100 return new BluetoothRouteProvider(context, btAdapter, listener); 101 } 102 BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter, BluetoothRoutesUpdatedListener listener)103 private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter, 104 BluetoothRoutesUpdatedListener listener) { 105 mContext = context; 106 mBluetoothAdapter = btAdapter; 107 mListener = listener; 108 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 109 buildBluetoothRoutes(); 110 } 111 start(UserHandle user)112 public void start(UserHandle user) { 113 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); 114 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID); 115 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO); 116 117 // Bluetooth on/off broadcasts 118 addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver()); 119 120 DeviceStateChangedReceiver deviceStateChangedReceiver = new DeviceStateChangedReceiver(); 121 addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver); 122 addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver); 123 addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED, 124 deviceStateChangedReceiver); 125 addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED, 126 deviceStateChangedReceiver); 127 addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED, 128 deviceStateChangedReceiver); 129 addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED, 130 deviceStateChangedReceiver); 131 132 mContext.registerReceiverAsUser(mBroadcastReceiver, user, 133 mIntentFilter, null, null); 134 } 135 stop()136 public void stop() { 137 mContext.unregisterReceiver(mBroadcastReceiver); 138 } 139 140 /** 141 * Transfers to a given bluetooth route. 142 * The dedicated BT device with the route would be activated. 143 * 144 * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of 145 * BT routes. 146 */ transferTo(@ullable String routeId)147 public void transferTo(@Nullable String routeId) { 148 if (routeId == null) { 149 clearActiveDevices(); 150 return; 151 } 152 153 BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId); 154 155 if (btRouteInfo == null) { 156 Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId); 157 return; 158 } 159 160 if (mBluetoothAdapter != null) { 161 mBluetoothAdapter.setActiveDevice(btRouteInfo.btDevice, ACTIVE_DEVICE_AUDIO); 162 } 163 } 164 165 /** 166 * Clears the active device for all known profiles. 167 */ clearActiveDevices()168 private void clearActiveDevices() { 169 if (mBluetoothAdapter != null) { 170 mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO); 171 } 172 } 173 addEventReceiver(String action, BluetoothEventReceiver eventReceiver)174 private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) { 175 mEventReceiverMap.put(action, eventReceiver); 176 mIntentFilter.addAction(action); 177 } 178 buildBluetoothRoutes()179 private void buildBluetoothRoutes() { 180 mBluetoothRoutes.clear(); 181 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); 182 if (bondedDevices != null) { 183 for (BluetoothDevice device : bondedDevices) { 184 if (device.isConnected()) { 185 BluetoothRouteInfo newBtRoute = createBluetoothRoute(device); 186 if (newBtRoute.connectedProfiles.size() > 0) { 187 mBluetoothRoutes.put(device.getAddress(), newBtRoute); 188 } 189 } 190 } 191 } 192 } 193 194 @Nullable getSelectedRoute()195 MediaRoute2Info getSelectedRoute() { 196 // For now, active routes can be multiple only when a pair of hearing aid devices is active. 197 // Let the first active device represent them. 198 return (mActiveRoutes.isEmpty() ? null : mActiveRoutes.get(0).route); 199 } 200 201 @NonNull getTransferableRoutes()202 List<MediaRoute2Info> getTransferableRoutes() { 203 List<MediaRoute2Info> routes = getAllBluetoothRoutes(); 204 for (BluetoothRouteInfo btRoute : mActiveRoutes) { 205 routes.remove(btRoute.route); 206 } 207 return routes; 208 } 209 210 @NonNull getAllBluetoothRoutes()211 List<MediaRoute2Info> getAllBluetoothRoutes() { 212 List<MediaRoute2Info> routes = new ArrayList<>(); 213 List<String> routeIds = new ArrayList<>(); 214 215 MediaRoute2Info selectedRoute = getSelectedRoute(); 216 if (selectedRoute != null) { 217 routes.add(selectedRoute); 218 routeIds.add(selectedRoute.getId()); 219 } 220 221 for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { 222 // A pair of hearing aid devices or having the same hardware address 223 if (routeIds.contains(btRoute.route.getId())) { 224 continue; 225 } 226 routes.add(btRoute.route); 227 routeIds.add(btRoute.route.getId()); 228 } 229 return routes; 230 } 231 findBluetoothRouteWithRouteId(String routeId)232 BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) { 233 if (routeId == null) { 234 return null; 235 } 236 for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) { 237 if (TextUtils.equals(btRouteInfo.route.getId(), routeId)) { 238 return btRouteInfo; 239 } 240 } 241 return null; 242 } 243 244 /** 245 * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}. 246 * 247 * @return true if devices can be handled by the provider. 248 */ updateVolumeForDevices(int devices, int volume)249 public boolean updateVolumeForDevices(int devices, int volume) { 250 int routeType; 251 if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) { 252 routeType = MediaRoute2Info.TYPE_HEARING_AID; 253 } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP 254 | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES 255 | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { 256 routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP; 257 } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) { 258 routeType = MediaRoute2Info.TYPE_BLE_HEADSET; 259 } else { 260 return false; 261 } 262 mVolumeMap.put(routeType, volume); 263 264 boolean shouldNotify = false; 265 for (BluetoothRouteInfo btRoute : mActiveRoutes) { 266 if (btRoute.route.getType() != routeType) { 267 continue; 268 } 269 btRoute.route = new MediaRoute2Info.Builder(btRoute.route) 270 .setVolume(volume) 271 .build(); 272 shouldNotify = true; 273 } 274 if (shouldNotify) { 275 notifyBluetoothRoutesUpdated(); 276 } 277 return true; 278 } 279 notifyBluetoothRoutesUpdated()280 private void notifyBluetoothRoutesUpdated() { 281 if (mListener != null) { 282 mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes()); 283 } 284 } 285 createBluetoothRoute(BluetoothDevice device)286 private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { 287 BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); 288 newBtRoute.btDevice = device; 289 290 String routeId = device.getAddress(); 291 String deviceName = device.getName(); 292 if (TextUtils.isEmpty(deviceName)) { 293 deviceName = mContext.getResources().getText(R.string.unknownName).toString(); 294 } 295 int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP; 296 newBtRoute.connectedProfiles = new SparseBooleanArray(); 297 if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) { 298 newBtRoute.connectedProfiles.put(BluetoothProfile.A2DP, true); 299 } 300 if (mHearingAidProfile != null 301 && mHearingAidProfile.getConnectedDevices().contains(device)) { 302 newBtRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true); 303 // Intentionally assign the same ID for a pair of devices to publish only one of them. 304 routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device); 305 type = MediaRoute2Info.TYPE_HEARING_AID; 306 } 307 if (mLeAudioProfile != null 308 && mLeAudioProfile.getConnectedDevices().contains(device)) { 309 newBtRoute.connectedProfiles.put(BluetoothProfile.LE_AUDIO, true); 310 routeId = LE_AUDIO_ROUTE_ID_PREFIX + mLeAudioProfile.getGroupId(device); 311 type = MediaRoute2Info.TYPE_BLE_HEADSET; 312 } 313 314 // Current volume will be set when connected. 315 newBtRoute.route = new MediaRoute2Info.Builder(routeId, deviceName) 316 .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) 317 .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK) 318 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) 319 .setDescription(mContext.getResources().getText( 320 R.string.bluetooth_a2dp_audio_route_name).toString()) 321 .setType(type) 322 .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) 323 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) 324 .setAddress(device.getAddress()) 325 .build(); 326 return newBtRoute; 327 } 328 setRouteConnectionState(@onNull BluetoothRouteInfo btRoute, @MediaRoute2Info.ConnectionState int state)329 private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute, 330 @MediaRoute2Info.ConnectionState int state) { 331 if (btRoute == null) { 332 Slog.w(TAG, "setRouteConnectionState: route shouldn't be null"); 333 return; 334 } 335 if (btRoute.route.getConnectionState() == state) { 336 return; 337 } 338 339 MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route) 340 .setConnectionState(state); 341 builder.setType(btRoute.getRouteType()); 342 343 if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) { 344 builder.setVolume(mVolumeMap.get(btRoute.getRouteType(), 0)); 345 } 346 btRoute.route = builder.build(); 347 } 348 addActiveRoute(BluetoothRouteInfo btRoute)349 private void addActiveRoute(BluetoothRouteInfo btRoute) { 350 if (btRoute == null) { 351 Slog.w(TAG, "addActiveRoute: btRoute is null"); 352 return; 353 } 354 if (DEBUG) { 355 Log.d(TAG, "Adding active route: " + btRoute.route); 356 } 357 if (mActiveRoutes.contains(btRoute)) { 358 Slog.w(TAG, "addActiveRoute: btRoute is already added."); 359 return; 360 } 361 setRouteConnectionState(btRoute, STATE_CONNECTED); 362 mActiveRoutes.add(btRoute); 363 } 364 removeActiveRoute(BluetoothRouteInfo btRoute)365 private void removeActiveRoute(BluetoothRouteInfo btRoute) { 366 if (DEBUG) { 367 Log.d(TAG, "Removing active route: " + btRoute.route); 368 } 369 if (mActiveRoutes.remove(btRoute)) { 370 setRouteConnectionState(btRoute, STATE_DISCONNECTED); 371 } 372 } 373 clearActiveRoutesWithType(int type)374 private void clearActiveRoutesWithType(int type) { 375 if (DEBUG) { 376 Log.d(TAG, "Clearing active routes with type. type=" + type); 377 } 378 Iterator<BluetoothRouteInfo> iter = mActiveRoutes.iterator(); 379 while (iter.hasNext()) { 380 BluetoothRouteInfo btRoute = iter.next(); 381 if (btRoute.route.getType() == type) { 382 iter.remove(); 383 setRouteConnectionState(btRoute, STATE_DISCONNECTED); 384 } 385 } 386 } 387 addActiveDevices(BluetoothDevice device)388 private void addActiveDevices(BluetoothDevice device) { 389 // Let the given device be the first active device 390 BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress()); 391 // This could happen if ACTION_ACTIVE_DEVICE_CHANGED is sent before 392 // ACTION_CONNECTION_STATE_CHANGED is sent. 393 if (activeBtRoute == null) { 394 activeBtRoute = createBluetoothRoute(device); 395 mBluetoothRoutes.put(device.getAddress(), activeBtRoute); 396 } 397 addActiveRoute(activeBtRoute); 398 399 // A bluetooth route with the same route ID should be added. 400 for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { 401 if (TextUtils.equals(btRoute.route.getId(), activeBtRoute.route.getId()) 402 && !TextUtils.equals(btRoute.btDevice.getAddress(), 403 activeBtRoute.btDevice.getAddress())) { 404 addActiveRoute(btRoute); 405 } 406 } 407 } addActiveHearingAidDevices(BluetoothDevice device)408 private void addActiveHearingAidDevices(BluetoothDevice device) { 409 if (DEBUG) { 410 Log.d(TAG, "Setting active hearing aid devices. device=" + device); 411 } 412 413 addActiveDevices(device); 414 } 415 addActiveLeAudioDevices(BluetoothDevice device)416 private void addActiveLeAudioDevices(BluetoothDevice device) { 417 if (DEBUG) { 418 Log.d(TAG, "Setting active le audio devices. device=" + device); 419 } 420 421 addActiveDevices(device); 422 } 423 424 interface BluetoothRoutesUpdatedListener { onBluetoothRoutesUpdated(@onNull List<MediaRoute2Info> routes)425 void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes); 426 } 427 428 private class BluetoothRouteInfo { 429 public BluetoothDevice btDevice; 430 public MediaRoute2Info route; 431 public SparseBooleanArray connectedProfiles; 432 433 @MediaRoute2Info.Type getRouteType()434 int getRouteType() { 435 // Let hearing aid profile have a priority. 436 if (connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) { 437 return MediaRoute2Info.TYPE_HEARING_AID; 438 } 439 440 if (connectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) { 441 return MediaRoute2Info.TYPE_BLE_HEADSET; 442 } 443 444 return MediaRoute2Info.TYPE_BLUETOOTH_A2DP; 445 } 446 } 447 448 // These callbacks run on the main thread. 449 private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener { onServiceConnected(int profile, BluetoothProfile proxy)450 public void onServiceConnected(int profile, BluetoothProfile proxy) { 451 List<BluetoothDevice> activeDevices; 452 switch (profile) { 453 case BluetoothProfile.A2DP: 454 mA2dpProfile = (BluetoothA2dp) proxy; 455 // It may contain null. 456 activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP); 457 break; 458 case BluetoothProfile.HEARING_AID: 459 mHearingAidProfile = (BluetoothHearingAid) proxy; 460 activeDevices = mBluetoothAdapter.getActiveDevices( 461 BluetoothProfile.HEARING_AID); 462 break; 463 case BluetoothProfile.LE_AUDIO: 464 mLeAudioProfile = (BluetoothLeAudio) proxy; 465 activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO); 466 break; 467 default: 468 return; 469 } 470 for (BluetoothDevice device : proxy.getConnectedDevices()) { 471 BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); 472 if (btRoute == null) { 473 btRoute = createBluetoothRoute(device); 474 mBluetoothRoutes.put(device.getAddress(), btRoute); 475 } 476 if (activeDevices.contains(device)) { 477 addActiveRoute(btRoute); 478 } 479 } 480 notifyBluetoothRoutesUpdated(); 481 } 482 onServiceDisconnected(int profile)483 public void onServiceDisconnected(int profile) { 484 switch (profile) { 485 case BluetoothProfile.A2DP: 486 mA2dpProfile = null; 487 break; 488 case BluetoothProfile.HEARING_AID: 489 mHearingAidProfile = null; 490 break; 491 case BluetoothProfile.LE_AUDIO: 492 mLeAudioProfile = null; 493 break; 494 default: 495 return; 496 } 497 } 498 } 499 private class BluetoothBroadcastReceiver extends BroadcastReceiver { 500 @Override onReceive(Context context, Intent intent)501 public void onReceive(Context context, Intent intent) { 502 String action = intent.getAction(); 503 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 504 505 BluetoothEventReceiver receiver = mEventReceiverMap.get(action); 506 if (receiver != null) { 507 receiver.onReceive(context, intent, device); 508 } 509 } 510 } 511 512 private interface BluetoothEventReceiver { onReceive(Context context, Intent intent, BluetoothDevice device)513 void onReceive(Context context, Intent intent, BluetoothDevice device); 514 } 515 516 private class AdapterStateChangedReceiver implements BluetoothEventReceiver { onReceive(Context context, Intent intent, BluetoothDevice device)517 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 518 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 519 if (state == BluetoothAdapter.STATE_OFF 520 || state == BluetoothAdapter.STATE_TURNING_OFF) { 521 mBluetoothRoutes.clear(); 522 notifyBluetoothRoutesUpdated(); 523 } else if (state == BluetoothAdapter.STATE_ON) { 524 buildBluetoothRoutes(); 525 if (!mBluetoothRoutes.isEmpty()) { 526 notifyBluetoothRoutesUpdated(); 527 } 528 } 529 } 530 } 531 532 private class DeviceStateChangedReceiver implements BluetoothEventReceiver { 533 @Override onReceive(Context context, Intent intent, BluetoothDevice device)534 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 535 switch (intent.getAction()) { 536 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: 537 clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); 538 if (device != null) { 539 addActiveRoute(mBluetoothRoutes.get(device.getAddress())); 540 } 541 notifyBluetoothRoutesUpdated(); 542 break; 543 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: 544 clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID); 545 if (device != null) { 546 addActiveHearingAidDevices(device); 547 } 548 notifyBluetoothRoutesUpdated(); 549 break; 550 case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: 551 clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET); 552 if (device != null) { 553 addActiveLeAudioDevices(device); 554 } 555 notifyBluetoothRoutesUpdated(); 556 break; 557 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 558 handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device); 559 break; 560 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: 561 handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device); 562 break; 563 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: 564 handleConnectionStateChanged(BluetoothProfile.LE_AUDIO, intent, device); 565 break; 566 } 567 } 568 handleConnectionStateChanged(int profile, Intent intent, BluetoothDevice device)569 private void handleConnectionStateChanged(int profile, Intent intent, 570 BluetoothDevice device) { 571 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 572 BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); 573 if (state == BluetoothProfile.STATE_CONNECTED) { 574 if (btRoute == null) { 575 btRoute = createBluetoothRoute(device); 576 if (btRoute.connectedProfiles.size() > 0) { 577 mBluetoothRoutes.put(device.getAddress(), btRoute); 578 notifyBluetoothRoutesUpdated(); 579 } 580 } else { 581 btRoute.connectedProfiles.put(profile, true); 582 } 583 } else if (state == BluetoothProfile.STATE_DISCONNECTING 584 || state == BluetoothProfile.STATE_DISCONNECTED) { 585 if (btRoute != null) { 586 btRoute.connectedProfiles.delete(profile); 587 if (btRoute.connectedProfiles.size() == 0) { 588 removeActiveRoute(mBluetoothRoutes.remove(device.getAddress())); 589 notifyBluetoothRoutesUpdated(); 590 } 591 } 592 } 593 } 594 } 595 } 596