• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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