1 /*
2  * Copyright 2019 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 package com.android.server.audio;
17 
18 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
19 import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET;
20 import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET;
21 import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET;
22 import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET;
23 import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET;
24 import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
25 import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
26 import static android.media.AudioSystem.isBluetoothA2dpOutDevice;
27 import static android.media.AudioSystem.isBluetoothDevice;
28 import static android.media.AudioSystem.isBluetoothLeOutDevice;
29 import static android.media.AudioSystem.isBluetoothOutDevice;
30 import static android.media.AudioSystem.isBluetoothScoOutDevice;
31 import static android.media.audio.Flags.automaticBtDeviceType;
32 
33 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
34 
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.bluetooth.BluetoothAdapter;
38 import android.bluetooth.BluetoothDevice;
39 import android.bluetooth.BluetoothLeAudio;
40 import android.bluetooth.BluetoothProfile;
41 import android.content.Intent;
42 import android.media.AudioDeviceAttributes;
43 import android.media.AudioDeviceInfo;
44 import android.media.AudioDevicePort;
45 import android.media.AudioFormat;
46 import android.media.AudioManager;
47 import android.media.AudioManager.AudioDeviceCategory;
48 import android.media.AudioPort;
49 import android.media.AudioRoutesInfo;
50 import android.media.AudioSystem;
51 import android.media.IAudioRoutesObserver;
52 import android.media.ICapturePresetDevicesRoleDispatcher;
53 import android.media.IStrategyNonDefaultDevicesDispatcher;
54 import android.media.IStrategyPreferredDevicesDispatcher;
55 import android.media.MediaMetrics;
56 import android.media.MediaRecorder.AudioSource;
57 import android.media.Utils;
58 import android.media.audiopolicy.AudioProductStrategy;
59 import android.media.permission.ClearCallingIdentityContext;
60 import android.media.permission.SafeCloseable;
61 import android.os.Binder;
62 import android.os.Bundle;
63 import android.os.RemoteCallbackList;
64 import android.os.RemoteException;
65 import android.os.SystemProperties;
66 import android.text.TextUtils;
67 import android.util.ArrayMap;
68 import android.util.ArraySet;
69 import android.util.Log;
70 import android.util.Pair;
71 import android.util.Slog;
72 
73 import com.android.internal.annotations.GuardedBy;
74 import com.android.internal.annotations.VisibleForTesting;
75 import com.android.server.utils.EventLogger;
76 
77 import com.google.android.collect.Sets;
78 
79 import java.io.PrintWriter;
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.Collection;
83 import java.util.HashSet;
84 import java.util.Iterator;
85 import java.util.LinkedHashMap;
86 import java.util.List;
87 import java.util.Map.Entry;
88 import java.util.Objects;
89 import java.util.Set;
90 import java.util.concurrent.atomic.AtomicBoolean;
91 import java.util.stream.Stream;
92 
93 /**
94  * Class to manage the inventory of all connected devices.
95  * This class is thread-safe.
96  * (non final for mocking/spying)
97  */
98 public class AudioDeviceInventory {
99 
100     private static final String TAG = "AS.AudioDeviceInventory";
101 
102     private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
103     private static final String SETTING_DEVICE_SEPARATOR = "\\|";
104 
105     /** Max String length that can be persisted within the Settings. */
106     // LINT.IfChange(settings_max_length_per_string)
107     private static final int MAX_SETTINGS_LENGTH_PER_STRING = 32768;
108     // LINT.ThenChange(/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java)
109 
110     private static final int MAX_DEVICE_INVENTORY_ENTRIES =
111             MAX_SETTINGS_LENGTH_PER_STRING / AdiDeviceState.getPeristedMaxSize();
112 
113     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
114     private final Object mDevicesLock = new Object();
115 
116     //Audio Analytics ids.
117     private static final String mMetricsId = "audio.device.";
118 
119     private final Object mDeviceInventoryLock = new Object();
120 
121     @GuardedBy("mDeviceInventoryLock")
122     private final LinkedHashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory =
123             new LinkedHashMap<>();
124 
getImmutableDeviceInventory()125     Collection<AdiDeviceState> getImmutableDeviceInventory() {
126         final List<AdiDeviceState> newList;
127         synchronized (mDeviceInventoryLock) {
128             newList = new ArrayList<>(mDeviceInventory.values());
129         }
130         return newList;
131     }
132 
133     /**
134      * Adds a new AdiDeviceState or updates the spatial audio related properties of the matching
135      * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
136      * @param deviceState the device to update
137      */
addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory)138     void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) {
139         synchronized (mDeviceInventoryLock) {
140             mDeviceInventory.merge(deviceState.getDeviceId(), deviceState,
141                     (oldState, newState) -> {
142                 oldState.setHasHeadTracker(newState.hasHeadTracker());
143                 oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled());
144                 oldState.setSAEnabled(newState.isSAEnabled());
145                 return oldState;
146             });
147             checkDeviceInventorySize_l();
148         }
149         if (syncInventory) {
150             mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
151         }
152     }
153 
154     /**
155      * Adds a new entry in mDeviceInventory if the attributes passed represent a sink
156      * Bluetooth device and no corresponding entry already exists.
157      *
158      * <p>This method will reconcile all BT devices connected with different profiles
159      * that share the same MAC address and will also synchronize the devices to their
160      * corresponding peers in case of BLE
161      */
addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress, @AudioDeviceCategory int category, boolean userDefined)162     void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
163             @AudioDeviceCategory int category, boolean userDefined) {
164         if (!isBluetoothOutDevice(deviceType)) {
165             return;
166         }
167         synchronized (mDeviceInventoryLock) {
168             AdiDeviceState ads = findBtDeviceStateForAddress(address, deviceType);
169             if (ads == null && peerAddress != null) {
170                 ads = findBtDeviceStateForAddress(peerAddress, deviceType);
171             }
172             if (ads != null) {
173                 // if category is user defined allow to change back to unknown otherwise
174                 // do not reset the category back to unknown since it might have been set
175                 // before by the user
176                 if (ads.getAudioDeviceCategory() != category && (userDefined
177                         || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
178                     ads.setAudioDeviceCategory(category);
179                     mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
180                     mDeviceBroker.postPersistAudioDeviceSettings();
181                 }
182                 mDeviceBroker.postSynchronizeAdiDevicesInInventory(ads);
183                 return;
184             }
185             ads = new AdiDeviceState(AudioDeviceInfo.convertInternalDeviceToDeviceType(deviceType),
186                     deviceType, address);
187             ads.setAudioDeviceCategory(category);
188 
189             mDeviceInventory.put(ads.getDeviceId(), ads);
190             checkDeviceInventorySize_l();
191 
192             mDeviceBroker.postUpdatedAdiDeviceState(ads, true /*initSA*/);
193             mDeviceBroker.postPersistAudioDeviceSettings();
194         }
195     }
196 
197     /**
198      * Adds a new AdiDeviceState or updates the audio device category of the matching
199      * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
200      * @param deviceState the device to update
201      */
addOrUpdateAudioDeviceCategoryInInventory( AdiDeviceState deviceState, boolean syncInventory)202     void addOrUpdateAudioDeviceCategoryInInventory(
203             AdiDeviceState deviceState, boolean syncInventory) {
204         AtomicBoolean updatedCategory = new AtomicBoolean(false);
205         synchronized (mDeviceInventoryLock) {
206             if (automaticBtDeviceType()) {
207                 if (deviceState.updateAudioDeviceCategory()) {
208                     updatedCategory.set(true);
209                 }
210             }
211             deviceState = mDeviceInventory.merge(deviceState.getDeviceId(),
212                     deviceState, (oldState, newState) -> {
213                         if (oldState.getAudioDeviceCategory()
214                                 != newState.getAudioDeviceCategory()) {
215                             oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory());
216                             updatedCategory.set(true);
217                         }
218                         return oldState;
219                     });
220             checkDeviceInventorySize_l();
221         }
222         if (updatedCategory.get()) {
223             mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/);
224         }
225         if (syncInventory) {
226             mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
227         }
228     }
229 
addAudioDeviceWithCategoryInInventoryIfNeeded(@onNull String address, @AudioDeviceCategory int btAudioDeviceCategory)230     void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
231             @AudioDeviceCategory int btAudioDeviceCategory) {
232         addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
233                 address, "", btAudioDeviceCategory, /*userDefined=*/true);
234         addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
235                 address, "", btAudioDeviceCategory, /*userDefined=*/true);
236 
237     }
238     @AudioDeviceCategory
getAndUpdateBtAdiDeviceStateCategoryForAddress(@onNull String address)239     int getAndUpdateBtAdiDeviceStateCategoryForAddress(@NonNull String address) {
240         int btCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
241         boolean bleCategoryFound = false;
242         AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET);
243         if (deviceState != null) {
244             addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/);
245             btCategory = deviceState.getAudioDeviceCategory();
246             bleCategoryFound = true;
247         }
248 
249         deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP);
250         if (deviceState != null) {
251             addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/);
252             int a2dpCategory = deviceState.getAudioDeviceCategory();
253             if (bleCategoryFound && a2dpCategory != btCategory) {
254                 Log.w(TAG, "Found different audio device category for A2DP and BLE profiles with "
255                         + "address " + address);
256             }
257             btCategory = a2dpCategory;
258         }
259 
260         return btCategory;
261     }
262 
isBluetoothAudioDeviceCategoryFixed(@onNull String address)263     boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) {
264         AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET);
265         if (deviceState != null) {
266             return deviceState.isBtDeviceCategoryFixed();
267         }
268 
269         deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP);
270         if (deviceState != null) {
271             return deviceState.isBtDeviceCategoryFixed();
272         }
273 
274         return false;
275     }
276 
277     /**
278      * Synchronize AdiDeviceState for LE devices in the same group
279      * or BT classic devices with the same address.
280      * @param updatedDevice the device state to synchronize or null.
281      * Called with null once after the device inventory and spatializer helper
282      * have been initialized to resync all devices.
283      */
onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice)284     void onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice) {
285         synchronized (mDevicesLock) {
286             synchronized (mDeviceInventoryLock) {
287                 if (updatedDevice != null) {
288                     onSynchronizeAdiDeviceInInventory_l(updatedDevice);
289                 } else {
290                     for (AdiDeviceState ads : mDeviceInventory.values()) {
291                         onSynchronizeAdiDeviceInInventory_l(ads);
292                     }
293                 }
294             }
295         }
296     }
297 
298     /**
299      * Synchronize AdiDeviceState for LE devices in the same group
300      * or BT classic devices with the same address.
301      * @param updatedDevice the device state to synchronize.
302      */
303     @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
onSynchronizeAdiDeviceInInventory_l(AdiDeviceState updatedDevice)304     void onSynchronizeAdiDeviceInInventory_l(AdiDeviceState updatedDevice) {
305         boolean found = false;
306         found |= synchronizeBleDeviceInInventory(updatedDevice);
307         if (automaticBtDeviceType()) {
308             found |= synchronizeDeviceProfilesInInventory(updatedDevice);
309         }
310         if (found) {
311             mDeviceBroker.postPersistAudioDeviceSettings();
312         }
313     }
314 
315     @GuardedBy("mDeviceInventoryLock")
checkDeviceInventorySize_l()316     private void checkDeviceInventorySize_l() {
317         if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) {
318             // remove the first element
319             Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator =
320                     mDeviceInventory.entrySet().iterator();
321             if (iterator.hasNext()) {
322                 iterator.next();
323                 iterator.remove();
324             }
325         }
326     }
327 
328     @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice)329     private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) {
330         for (DeviceInfo di : mConnectedDevices.values()) {
331             if (di.mDeviceType != updatedDevice.getInternalDeviceType()) {
332                 continue;
333             }
334             if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
335                 for (AdiDeviceState ads2 : mDeviceInventory.values()) {
336                     if (!(di.mDeviceType == ads2.getInternalDeviceType()
337                             && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
338                         continue;
339                     }
340                     if (mDeviceBroker.isSADevice(updatedDevice)
341                             == mDeviceBroker.isSADevice(ads2)) {
342                         ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
343                         ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
344                         ads2.setSAEnabled(updatedDevice.isSAEnabled());
345                     }
346                     ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
347 
348                     mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/);
349                     AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
350                             "synchronizeBleDeviceInInventory synced device pair ads1="
351                                     + updatedDevice + " ads2=" + ads2).printLog(TAG));
352                     return true;
353                 }
354             }
355             if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
356                 for (AdiDeviceState ads2 : mDeviceInventory.values()) {
357                     if (!(di.mDeviceType == ads2.getInternalDeviceType()
358                             && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
359                         continue;
360                     }
361                     if (mDeviceBroker.isSADevice(updatedDevice)
362                             == mDeviceBroker.isSADevice(ads2)) {
363                         ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
364                         ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
365                         ads2.setSAEnabled(updatedDevice.isSAEnabled());
366                     }
367                     ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
368 
369                     mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/);
370                     AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
371                             "synchronizeBleDeviceInInventory synced device pair ads1="
372                                     + updatedDevice + " peer ads2=" + ads2).printLog(TAG));
373                     return true;
374                 }
375             }
376         }
377         return false;
378     }
379 
380     @GuardedBy("mDeviceInventoryLock")
synchronizeDeviceProfilesInInventory(AdiDeviceState updatedDevice)381     private boolean synchronizeDeviceProfilesInInventory(AdiDeviceState updatedDevice) {
382         for (AdiDeviceState ads : mDeviceInventory.values()) {
383             if (updatedDevice.getInternalDeviceType() == ads.getInternalDeviceType()
384                     || !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
385                 continue;
386             }
387             if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
388                 ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
389                 ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
390                 ads.setSAEnabled(updatedDevice.isSAEnabled());
391             }
392             ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
393 
394             mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
395             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
396                     "synchronizeDeviceProfilesInInventory synced device pair ads1="
397                             + updatedDevice + " ads2=" + ads).printLog(TAG));
398             return true;
399         }
400         return false;
401     }
402 
403     /**
404      * Finds the BT device that matches the passed {@code address}. Currently, this method only
405      * returns a valid device for A2DP and BLE devices.
406      *
407      * @param address MAC address of BT device
408      * @param deviceType internal device type to identify the BT device
409      * @return the found {@link AdiDeviceState} or {@code null} otherwise.
410      */
411     @Nullable
412     @VisibleForTesting(visibility = PACKAGE)
findBtDeviceStateForAddress(String address, int deviceType)413     public AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
414         Set<Integer> deviceSet;
415         if (isBluetoothA2dpOutDevice(deviceType)) {
416             deviceSet = DEVICE_OUT_ALL_A2DP_SET;
417         } else if (isBluetoothLeOutDevice(deviceType)) {
418             deviceSet = DEVICE_OUT_ALL_BLE_SET;
419         } else if (isBluetoothScoOutDevice(deviceType)) {
420             deviceSet = DEVICE_OUT_ALL_SCO_SET;
421         } else if (deviceType == DEVICE_OUT_HEARING_AID) {
422             deviceSet = new HashSet<>();
423             deviceSet.add(DEVICE_OUT_HEARING_AID);
424         } else {
425             return null;
426         }
427         synchronized (mDeviceInventoryLock) {
428             for (Integer internalType : deviceSet) {
429                 AdiDeviceState deviceState = mDeviceInventory.get(
430                         new Pair<>(internalType, address));
431                 if (deviceState != null) {
432                     return deviceState;
433                 }
434             }
435         }
436         return null;
437     }
438 
439     /**
440      * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device
441      * type. Note: currently this method only returns a valid device for A2DP and BLE devices.
442      *
443      * @param ada attributes of device to match
444      * @param canonicalDeviceType external device type to match
445      * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or
446      *         {@code null} otherwise.
447      */
448     @Nullable
findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, int canonicalDeviceType)449     AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
450             int canonicalDeviceType) {
451         final boolean isWireless = isBluetoothDevice(ada.getInternalType());
452         synchronized (mDeviceInventoryLock) {
453             for (AdiDeviceState deviceState : mDeviceInventory.values()) {
454                 if (deviceState.getDeviceType() == canonicalDeviceType
455                         && (!isWireless || ada.getAddress().equals(
456                         deviceState.getDeviceAddress()))) {
457                     return deviceState;
458                 }
459             }
460         }
461         return null;
462     }
463 
464     /** Clears all cached {@link AdiDeviceState}'s. */
clearDeviceInventory()465     void clearDeviceInventory() {
466         synchronized (mDeviceInventoryLock) {
467             mDeviceInventory.clear();
468         }
469     }
470 
471     // List of connected devices
472     // Key for map created from DeviceInfo.makeDeviceListKey()
473     @GuardedBy("mDevicesLock")
474     private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>() {
475         @Override
476         public DeviceInfo put(String key, DeviceInfo value) {
477             final DeviceInfo result = super.put(key, value);
478             record("put", true /* connected */, value);
479             return result;
480         }
481 
482         @Override
483         public DeviceInfo putIfAbsent(String key, DeviceInfo value) {
484             final DeviceInfo result = super.putIfAbsent(key, value);
485             if (result == null) {
486                 record("putIfAbsent", true /* connected */, value);
487             }
488             return result;
489         }
490 
491         @Override
492         public DeviceInfo remove(Object key) {
493             final DeviceInfo result = super.remove(key);
494             if (result != null) {
495                 record("remove", false /* connected */, result);
496             }
497             return result;
498         }
499 
500         @Override
501         public boolean remove(Object key, Object value) {
502             final boolean result = super.remove(key, value);
503             if (result) {
504                 record("remove", false /* connected */, (DeviceInfo) value);
505             }
506             return result;
507         }
508 
509         // Not overridden
510         // clear
511         // compute
512         // computeIfAbsent
513         // computeIfPresent
514         // merge
515         // putAll
516         // replace
517         // replaceAll
518         private void record(String event, boolean connected, DeviceInfo value) {
519             // DeviceInfo - int mDeviceType;
520             // DeviceInfo - int mDeviceCodecFormat;
521             new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
522                     + MediaMetrics.SEPARATOR + AudioSystem.getDeviceName(value.mDeviceType))
523                     .set(MediaMetrics.Property.ADDRESS, value.mDeviceAddress)
524                     .set(MediaMetrics.Property.EVENT, event)
525                     .set(MediaMetrics.Property.NAME, value.mDeviceName)
526                     .set(MediaMetrics.Property.STATE, connected
527                             ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
528                     .record();
529         }
530     };
531 
532     // List of devices actually connected to AudioPolicy (through AudioSystem), only one
533     // by device type, which is used as the key, value is the DeviceInfo generated key.
534     // For the moment only for A2DP sink devices.
535     // TODO: extend to all device types
536     @GuardedBy("mDevicesLock")
537     private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
538 
539     // List of preferred devices for strategies
540     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices =
541             new ArrayMap<>();
542 
543     // List of non-default devices for strategies
544     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mNonDefaultDevices =
545             new ArrayMap<>();
546 
547     // List of preferred devices of capture preset
548     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset =
549             new ArrayMap<>();
550 
551     // the wrapper for AudioSystem static methods, allows us to spy AudioSystem
552     private final @NonNull AudioSystemAdapter mAudioSystem;
553 
554     private @NonNull AudioDeviceBroker mDeviceBroker;
555 
556     // Monitoring of audio routes.  Protected by mAudioRoutes.
557     final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
558     final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
559             new RemoteCallbackList<IAudioRoutesObserver>();
560 
561     // Monitoring of preferred device for strategies
562     final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers =
563             new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>();
564 
565     // Monitoring of non-default device for strategies
566     final RemoteCallbackList<IStrategyNonDefaultDevicesDispatcher> mNonDefDevDispatchers =
567             new RemoteCallbackList<IStrategyNonDefaultDevicesDispatcher>();
568 
569     // Monitoring of devices for role and capture preset
570     final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
571             new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
572 
573     final List<AudioProductStrategy> mStrategies;
574 
AudioDeviceInventory(@onNull AudioDeviceBroker broker)575     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
576         this(broker, AudioSystemAdapter.getDefaultAdapter());
577     }
578 
579     //-----------------------------------------------------------
580     /** for mocking only, allows to inject AudioSystem adapter */
AudioDeviceInventory(@onNull AudioSystemAdapter audioSystem)581     /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) {
582         this(null, audioSystem);
583     }
584 
AudioDeviceInventory(@ullable AudioDeviceBroker broker, @Nullable AudioSystemAdapter audioSystem)585     private AudioDeviceInventory(@Nullable AudioDeviceBroker broker,
586                        @Nullable AudioSystemAdapter audioSystem) {
587         mDeviceBroker = broker;
588         mAudioSystem = audioSystem;
589         mStrategies = AudioProductStrategy.getAudioProductStrategies();
590         mBluetoothDualModeEnabled = SystemProperties.getBoolean(
591                 "persist.bluetooth.enable_dual_mode_audio", false);
592     }
setDeviceBroker(@onNull AudioDeviceBroker broker)593     /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
594         mDeviceBroker = broker;
595     }
596 
597     //------------------------------------------------------------
598     /**
599      * Class to store info about connected devices.
600      * Use makeDeviceListKey() to make a unique key for this list.
601      */
602     private static class DeviceInfo {
603         final int mDeviceType;
604         final @NonNull String mDeviceName;
605         final @NonNull String mDeviceAddress;
606         @NonNull String mDeviceIdentityAddress;
607         int mDeviceCodecFormat;
608         final int mGroupId;
609         @NonNull String mPeerDeviceAddress;
610         @NonNull String mPeerIdentityDeviceAddress;
611 
612         /** Disabled operating modes for this device. Use a negative logic so that by default
613          * an empty list means all modes are allowed.
614          * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
615         @NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
616 
DeviceInfo(int deviceType, String deviceName, String address, String identityAddress, int codecFormat, int groupId, String peerAddress, String peerIdentityAddress)617         DeviceInfo(int deviceType, String deviceName, String address,
618                    String identityAddress, int codecFormat,
619                    int groupId, String peerAddress, String peerIdentityAddress) {
620             mDeviceType = deviceType;
621             mDeviceName = TextUtils.emptyIfNull(deviceName);
622             mDeviceAddress = TextUtils.emptyIfNull(address);
623             mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress);
624             if (mDeviceIdentityAddress.isEmpty()) {
625                 mDeviceIdentityAddress = mDeviceAddress;
626             }
627             mDeviceCodecFormat = codecFormat;
628             mGroupId = groupId;
629             mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
630             mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(peerIdentityAddress);
631         }
632 
633         /** Constructor for all devices except A2DP sink and LE Audio */
DeviceInfo(int deviceType, String deviceName, String address)634         DeviceInfo(int deviceType, String deviceName, String address) {
635             this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT);
636         }
637 
638         /** Constructor for A2DP sink devices */
DeviceInfo(int deviceType, String deviceName, String address, String identityAddress, int codecFormat)639         DeviceInfo(int deviceType, String deviceName, String address,
640                    String identityAddress, int codecFormat) {
641             this(deviceType, deviceName, address, identityAddress, codecFormat,
642                     BluetoothLeAudio.GROUP_ID_INVALID, null, null);
643         }
644 
setModeDisabled(String mode)645         void setModeDisabled(String mode) {
646             mDisabledModes.add(mode);
647         }
setModeEnabled(String mode)648         void setModeEnabled(String mode) {
649             mDisabledModes.remove(mode);
650         }
isModeEnabled(String mode)651         boolean isModeEnabled(String mode) {
652             return !mDisabledModes.contains(mode);
653         }
isOutputOnlyModeEnabled()654         boolean isOutputOnlyModeEnabled() {
655             return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
656         }
isDuplexModeEnabled()657         boolean isDuplexModeEnabled() {
658             return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
659         }
660 
661         @Override
toString()662         public String toString() {
663             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
664                     + " (" + AudioSystem.getDeviceName(mDeviceType)
665                     + ") name:" + mDeviceName
666                     + " addr:" + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceAddress)
667                     + " identity addr:"
668                     + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceIdentityAddress)
669                     + " codec: " + Integer.toHexString(mDeviceCodecFormat)
670                     + " group:" + mGroupId
671                     + " peer addr:"
672                     + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerDeviceAddress)
673                     + " peer identity addr:"
674                     + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerIdentityDeviceAddress)
675                     + " disabled modes: " + mDisabledModes + "]";
676         }
677 
getKey()678         @NonNull String getKey() {
679             return makeDeviceListKey(mDeviceType, mDeviceAddress);
680         }
681 
682         /**
683          * Generate a unique key for the mConnectedDevices List by composing the device "type"
684          * and the "address" associated with a specific instance of that device type
685          */
makeDeviceListKey(int device, String deviceAddress)686         @NonNull private static String makeDeviceListKey(int device, String deviceAddress) {
687             return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
688         }
689     }
690 
691     /**
692      * A class just for packaging up a set of connection parameters.
693      */
694     /*package*/ static class WiredDeviceConnectionState {
695         public final AudioDeviceAttributes mAttributes;
696         public final @AudioService.ConnectionState int mState;
697         public final String mCaller;
698         public boolean mForTest = false;
699 
WiredDeviceConnectionState(AudioDeviceAttributes attributes, @AudioService.ConnectionState int state, String caller)700         /*package*/ WiredDeviceConnectionState(AudioDeviceAttributes attributes,
701                 @AudioService.ConnectionState int state, String caller) {
702             mAttributes = attributes;
703             mState = state;
704             mCaller = caller;
705         }
706     }
707 
708     //------------------------------------------------------------
dump(PrintWriter pw, String prefix)709     /*package*/ void dump(PrintWriter pw, String prefix) {
710         pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET=");
711         BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> {
712             pw.print(" 0x" +  Integer.toHexString(device)); });
713         pw.println("\n" + prefix + "Preferred devices for strategy:");
714         mPreferredDevices.forEach((strategy, device) -> {
715             pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
716         pw.println("\n" + prefix + "Non-default devices for strategy:");
717         mNonDefaultDevices.forEach((strategy, device) -> {
718             pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
719         pw.println("\n" + prefix + "Connected devices:");
720         mConnectedDevices.forEach((key, deviceInfo) -> {
721             pw.println("  " + prefix + deviceInfo.toString()); });
722         pw.println("\n" + prefix + "APM Connected device (A2DP sink only):");
723         mApmConnectedDevices.forEach((keyType, valueAddress) -> {
724             pw.println("  " + prefix + " type:0x" + Integer.toHexString(keyType)
725                     + " (" + AudioSystem.getDeviceName(keyType)
726                     + ") addr:" + Utils.anonymizeBluetoothAddress(keyType, valueAddress)); });
727         pw.println("\n" + prefix + "Preferred devices for capture preset:");
728         mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
729             pw.println("  " + prefix + "capturePreset:" + capturePreset
730                     + " devices:" + devices); });
731         pw.println("\n" + prefix + "Applied devices roles for strategies (from API):");
732         mAppliedStrategyRoles.forEach((key, devices) -> {
733             pw.println("  " + prefix + "strategy: " + key.first
734                     +  " role:" + key.second + " devices:" + devices); });
735         pw.println("\n" + prefix + "Applied devices roles for strategies (internal):");
736         mAppliedStrategyRolesInt.forEach((key, devices) -> {
737             pw.println("  " + prefix + "strategy: " + key.first
738                     +  " role:" + key.second + " devices:" + devices); });
739         pw.println("\n" + prefix + "Applied devices roles for presets (from API):");
740         mAppliedPresetRoles.forEach((key, devices) -> {
741             pw.println("  " + prefix + "preset: " + key.first
742                     +  " role:" + key.second + " devices:" + devices); });
743         pw.println("\n" + prefix + "Applied devices roles for presets (internal:");
744         mAppliedPresetRolesInt.forEach((key, devices) -> {
745             pw.println("  " + prefix + "preset: " + key.first
746                     +  " role:" + key.second + " devices:" + devices); });
747         pw.println("\ndevices:\n");
748         synchronized (mDeviceInventoryLock) {
749             for (AdiDeviceState device : mDeviceInventory.values()) {
750                 pw.println("\t" + device + "\n");
751             }
752         }
753     }
754 
755     //------------------------------------------------------------
756     // Message handling from AudioDeviceBroker
757 
758     /**
759      * Restore previously connected devices. Use in case of audio server crash
760      * (see AudioService.onAudioServerDied() method)
761      */
762     // Always executed on AudioDeviceBroker message queue
onRestoreDevices()763     /*package*/ void onRestoreDevices() {
764         synchronized (mDevicesLock) {
765             //TODO iterate on mApmConnectedDevices instead once it handles all device types
766             for (DeviceInfo di : mConnectedDevices.values()) {
767                 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType,
768                         di.mDeviceAddress,
769                         di.mDeviceName),
770                         AudioSystem.DEVICE_STATE_AVAILABLE,
771                         di.mDeviceCodecFormat);
772             }
773             mAppliedStrategyRolesInt.clear();
774             mAppliedPresetRolesInt.clear();
775             applyConnectedDevicesRoles_l();
776         }
777         reapplyExternalDevicesRoles();
778     }
779 
reapplyExternalDevicesRoles()780     /*package*/ void reapplyExternalDevicesRoles() {
781         synchronized (mDevicesLock) {
782             mAppliedStrategyRoles.clear();
783             mAppliedPresetRoles.clear();
784         }
785         synchronized (mPreferredDevices) {
786             mPreferredDevices.forEach((strategy, devices) -> {
787                 setPreferredDevicesForStrategy(strategy, devices);
788             });
789         }
790         synchronized (mNonDefaultDevices) {
791             mNonDefaultDevices.forEach((strategy, devices) -> {
792                 addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED,
793                         devices, false /* internal */);
794             });
795         }
796         synchronized (mPreferredDevicesForCapturePreset) {
797             mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
798                 setDevicesRoleForCapturePreset(
799                         capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
800             });
801         }
802     }
803 
804     /** only public for mocking/spying, do not call outside of AudioService */
805     // @GuardedBy("mDeviceBroker.mSetModeLock")
806     @VisibleForTesting
807     //@GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
onSetBtActiveDevice(@onNull AudioDeviceBroker.BtDeviceInfo btInfo, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int streamType)808     public void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
809                                     @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
810                                     int streamType) {
811         if (AudioService.DEBUG_DEVICES) {
812             Log.d(TAG, "onSetBtActiveDevice"
813                     + " btDevice=" + btInfo.mDevice
814                     + " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile)
815                     + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState));
816         }
817         String address = btInfo.mDevice.getAddress();
818         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
819             address = "";
820         }
821 
822         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:"
823                         + btInfo + " codec=" + AudioSystem.audioFormatToString(codec)));
824 
825         new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice")
826                 .set(MediaMetrics.Property.STATUS, btInfo.mProfile)
827                 .set(MediaMetrics.Property.DEVICE,
828                         AudioSystem.getDeviceName(btInfo.mAudioSystemDevice))
829                 .set(MediaMetrics.Property.ADDRESS, address)
830                 .set(MediaMetrics.Property.ENCODING,
831                         AudioSystem.audioFormatToString(codec))
832                 .set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice")
833                 .set(MediaMetrics.Property.STREAM_TYPE,
834                         AudioSystem.streamToString(streamType))
835                 .set(MediaMetrics.Property.STATE,
836                         btInfo.mState == BluetoothProfile.STATE_CONNECTED
837                         ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
838                 .record();
839 
840         synchronized (mDevicesLock) {
841             final String key = DeviceInfo.makeDeviceListKey(btInfo.mAudioSystemDevice, address);
842             final DeviceInfo di = mConnectedDevices.get(key);
843 
844             final boolean isConnected = di != null;
845 
846             final boolean switchToUnavailable = isConnected
847                     && btInfo.mState != BluetoothProfile.STATE_CONNECTED;
848             final boolean switchToAvailable = !isConnected
849                     && btInfo.mState == BluetoothProfile.STATE_CONNECTED;
850 
851             switch (btInfo.mProfile) {
852                 case BluetoothProfile.A2DP_SINK:
853                     if (switchToUnavailable) {
854                         makeA2dpSrcUnavailable(address);
855                     } else if (switchToAvailable) {
856                         makeA2dpSrcAvailable(address);
857                     }
858                     break;
859                 case BluetoothProfile.A2DP:
860                     if (switchToUnavailable) {
861                         makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
862                     } else if (switchToAvailable) {
863                         // device is not already connected
864                         if (btInfo.mVolume != -1) {
865                             mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
866                                     // convert index to internal representation in VolumeStreamState
867                                     btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
868                                     "onSetBtActiveDevice");
869                         }
870                         makeA2dpDeviceAvailable(btInfo, codec, "onSetBtActiveDevice");
871                     }
872                     break;
873                 case BluetoothProfile.HEARING_AID:
874                     if (switchToUnavailable) {
875                         makeHearingAidDeviceUnavailable(address);
876                     } else if (switchToAvailable) {
877                         makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
878                                 streamType, "onSetBtActiveDevice");
879                     }
880                     break;
881                 case BluetoothProfile.LE_AUDIO:
882                 case BluetoothProfile.LE_AUDIO_BROADCAST:
883                     if (switchToUnavailable) {
884                         makeLeAudioDeviceUnavailableNow(address,
885                                 btInfo.mAudioSystemDevice, di.mDeviceCodecFormat);
886                     } else if (switchToAvailable) {
887                         makeLeAudioDeviceAvailable(
888                                 btInfo, streamType, codec, "onSetBtActiveDevice");
889                     }
890                     break;
891                 case BluetoothProfile.HEADSET:
892                     if (mDeviceBroker.isScoManagedByAudio()) {
893                         if (switchToUnavailable) {
894                             mDeviceBroker.onSetBtScoActiveDevice(null);
895                         } else if (switchToAvailable) {
896                             mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice);
897                         }
898                     }
899                     break;
900                 default: throw new IllegalArgumentException("Invalid profile "
901                                  + BluetoothProfile.getProfileName(btInfo.mProfile));
902             }
903         }
904     }
905 
906     // Additional delay added to the music mute duration when a codec config change is executed.
907     static final int BT_CONFIG_CHANGE_MUTE_DELAY_MS = 500;
908 
909     /**
910      * Handles a Bluetooth link codec configuration change communicated by the Bluetooth stack.
911      * Called when either A2DP or LE Audio codec encoding or sampling rate changes:
912      * the change is communicated to native audio policy to eventually reconfigure the audio
913      * path.
914      * Also used to notify a change in preferred mode (duplex or output) for Bluetooth profiles.
915      *
916      * @param btInfo contains all information on the Bluetooth device and profile
917      * @param codec the requested audio encoding (e.g SBC)
918      * @param codecChanged true if a codec parameter changed, false for preferred mode change
919      * @param event currently only EVENT_DEVICE_CONFIG_CHANGE
920      * @return an optional additional delay in milliseconds to add to the music mute period in
921      * case of an actual codec reconfiguration.
922      */
923     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onBluetoothDeviceConfigChange( @onNull AudioDeviceBroker.BtDeviceInfo btInfo, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, boolean codecChanged, int event)924     /*package*/ int onBluetoothDeviceConfigChange(
925             @NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
926             @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
927             boolean codecChanged, int event) {
928         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
929                 + "onBluetoothDeviceConfigChange")
930                 .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
931 
932         int delayMs = 0;
933         final BluetoothDevice btDevice = btInfo.mDevice;
934         if (btDevice == null) {
935             mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
936             return delayMs;
937         }
938         if (AudioService.DEBUG_DEVICES) {
939             Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
940         }
941         int volume = btInfo.mVolume;
942 
943         String address = btDevice.getAddress();
944         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
945             address = "";
946         }
947         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
948                 "onBluetoothDeviceConfigChange addr=" + address
949                     + " event=" + BtHelper.deviceEventToString(event)));
950 
951         synchronized (mDevicesLock) {
952             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
953                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
954                         "A2dp config change ignored (scheduled connection change)")
955                         .printSlog(EventLogger.Event.ALOGI, TAG));
956                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
957                         .record();
958                 return delayMs;
959             }
960             final String key = DeviceInfo.makeDeviceListKey(
961                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
962             final DeviceInfo di = mConnectedDevices.get(key);
963             if (di == null) {
964                 Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
965                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
966                 return delayMs;
967             }
968 
969             mmi.set(MediaMetrics.Property.ADDRESS, address)
970                     .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
971                     .set(MediaMetrics.Property.INDEX, volume)
972                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
973 
974             if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
975                 if (btInfo.mProfile == BluetoothProfile.A2DP
976                         || btInfo.mProfile == BluetoothProfile.LE_AUDIO
977                         || btInfo.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) {
978                     if (codecChanged) {
979                         di.mDeviceCodecFormat = codec;
980                         mConnectedDevices.replace(key, di);
981                         final int res = mAudioSystem.handleDeviceConfigChange(
982                                 btInfo.mAudioSystemDevice, address,
983                                 BtHelper.getName(btDevice), codec);
984                         if (res != AudioSystem.AUDIO_STATUS_OK) {
985                             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
986                                     "APM handleDeviceConfigChange failed for A2DP device addr="
987                                             + address + " codec="
988                                             + AudioSystem.audioFormatToString(codec))
989                                     .printSlog(EventLogger.Event.ALOGE, TAG));
990 
991                             // force A2DP device disconnection in case of error so that AudioService
992                             // state is consistent with audio policy manager state
993                             setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
994                                     BluetoothProfile.STATE_DISCONNECTED));
995                         } else {
996                             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
997                                     "APM handleDeviceConfigChange success for A2DP device addr="
998                                             + address
999                                             + " codec=" + AudioSystem.audioFormatToString(codec))
1000                                     .printSlog(EventLogger.Event.ALOGI, TAG));
1001                             delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS;
1002                         }
1003                     }
1004                 }
1005                 if (!codecChanged) {
1006                     updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
1007                 }
1008             }
1009         }
1010         mmi.record();
1011         return delayMs;
1012     }
1013 
onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec)1014     /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
1015         synchronized (mDevicesLock) {
1016             makeA2dpDeviceUnavailableNow(address, a2dpCodec);
1017         }
1018     }
1019 
onMakeLeAudioDeviceUnavailableNow(String address, int device, int codec)1020     /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device, int codec) {
1021         synchronized (mDevicesLock) {
1022             makeLeAudioDeviceUnavailableNow(address, device, codec);
1023         }
1024     }
1025 
1026 
1027     /**
1028      * Goes over all connected LE Audio devices in the provided group ID and
1029      * update:
1030      * - the peer address according to the addres of other device in the same
1031      * group (can also clear the peer address is not anymore in the group)
1032      * - The dentity address if not yet set.
1033      * LE Audio buds in a pair are in the same group.
1034      * @param groupId the LE Audio group to update
1035      */
onUpdateLeAudioGroupAddresses(int groupId)1036     /*package*/ void onUpdateLeAudioGroupAddresses(int groupId) {
1037         synchronized (mDevicesLock) {
1038             // <address, identy address>
1039             List<Pair<String, String>> addresses = new ArrayList<>();
1040             for (DeviceInfo di : mConnectedDevices.values()) {
1041                 if (di.mGroupId == groupId) {
1042                     if (addresses.isEmpty()) {
1043                         addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
1044                     }
1045                     if (di.mPeerDeviceAddress.equals("")) {
1046                         for (Pair<String, String> addr : addresses) {
1047                             if (!di.mDeviceAddress.equals(addr.first)) {
1048                                 di.mPeerDeviceAddress = TextUtils.emptyIfNull(addr.first);
1049                                 di.mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(addr.second);
1050                                 break;
1051                             }
1052                         }
1053                     } else if (!addresses.contains(
1054                             new Pair(di.mPeerDeviceAddress, di.mPeerIdentityDeviceAddress))) {
1055                         di.mPeerDeviceAddress = "";
1056                         di.mPeerIdentityDeviceAddress = "";
1057                     }
1058                     if (di.mDeviceIdentityAddress.equals("")) {
1059                         for (Pair<String, String> addr : addresses) {
1060                             if (di.mDeviceAddress.equals(addr.first)) {
1061                                 di.mDeviceIdentityAddress = TextUtils.emptyIfNull(addr.second);
1062                                 break;
1063                             }
1064                         }
1065                     }
1066                 }
1067             }
1068         }
1069     }
1070 
onReportNewRoutes()1071     /*package*/ void onReportNewRoutes() {
1072         int n = mRoutesObservers.beginBroadcast();
1073         if (n > 0) {
1074             new MediaMetrics.Item(mMetricsId + "onReportNewRoutes")
1075                     .set(MediaMetrics.Property.OBSERVERS, n)
1076                     .record();
1077             AudioRoutesInfo routes;
1078             synchronized (mCurAudioRoutes) {
1079                 routes = new AudioRoutesInfo(mCurAudioRoutes);
1080             }
1081             while (n > 0) {
1082                 n--;
1083                 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
1084                 try {
1085                     obs.dispatchAudioRoutesChanged(routes);
1086                 } catch (RemoteException e) {
1087                     Log.e(TAG, "onReportNewRoutes", e);
1088                 }
1089             }
1090         }
1091         mRoutesObservers.finishBroadcast();
1092         mDeviceBroker.postObserveDevicesForAllStreams();
1093     }
1094 
1095     /* package */ static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET;
1096     static {
1097         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>();
1098         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
1099         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
1100         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE);
1101         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
1102     }
1103 
onSetWiredDeviceConnectionState( AudioDeviceInventory.WiredDeviceConnectionState wdcs)1104     /*package*/ void onSetWiredDeviceConnectionState(
1105                             AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
1106         int type = wdcs.mAttributes.getInternalType();
1107 
1108         AudioService.sDeviceLogger.enqueue(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
1109 
1110         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
1111                 + "onSetWiredDeviceConnectionState")
1112                 .set(MediaMetrics.Property.ADDRESS, wdcs.mAttributes.getAddress())
1113                 .set(MediaMetrics.Property.DEVICE,
1114                         AudioSystem.getDeviceName(type))
1115                 .set(MediaMetrics.Property.STATE,
1116                         wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
1117                                 ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED);
1118         AudioDeviceInfo info = null;
1119         if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
1120                 && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
1121                         wdcs.mAttributes.getInternalType())) {
1122             for (AudioDeviceInfo deviceInfo : AudioManager.getDevicesStatic(
1123                     AudioManager.GET_DEVICES_OUTPUTS)) {
1124                 if (deviceInfo.getInternalType() == wdcs.mAttributes.getInternalType()) {
1125                     info = deviceInfo;
1126                     break;
1127                 }
1128             }
1129         }
1130         synchronized (mDevicesLock) {
1131             if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
1132                     && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
1133                 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/,
1134                         "onSetWiredDeviceConnectionState state DISCONNECTED");
1135             }
1136 
1137             if (!handleDeviceConnection(wdcs.mAttributes,
1138                     wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
1139                 // change of connection state failed, bailout
1140                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
1141                         .record();
1142                 return;
1143             }
1144             if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
1145                 if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
1146                     mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/,
1147                             "onSetWiredDeviceConnectionState state not DISCONNECTED");
1148                 }
1149                 mDeviceBroker.checkMusicActive(type, wdcs.mCaller);
1150             }
1151             if (type == AudioSystem.DEVICE_OUT_HDMI) {
1152                 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
1153             }
1154             if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
1155                     && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
1156                             wdcs.mAttributes.getInternalType())) {
1157                 if (info != null) {
1158                     mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(
1159                             info);
1160                 } else {
1161                     Log.e(TAG, "Didn't find AudioDeviceInfo to notify preferred mixer "
1162                             + "attributes change for type=" + wdcs.mAttributes.getType());
1163                 }
1164             }
1165             sendDeviceConnectionIntent(type, wdcs.mState,
1166                     wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
1167             updateAudioRoutes(type, wdcs.mState);
1168         }
1169         mmi.record();
1170     }
1171 
onToggleHdmi()1172     /*package*/ void onToggleHdmi() {
1173         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
1174                 .set(MediaMetrics.Property.DEVICE,
1175                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI));
1176         synchronized (mDevicesLock) {
1177             // Is HDMI connected?
1178             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
1179             final DeviceInfo di = mConnectedDevices.get(key);
1180             if (di == null) {
1181                 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
1182                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record();
1183                 return;
1184             }
1185             // Toggle HDMI to retrigger broadcast with proper formats.
1186             setWiredDeviceConnectionState(
1187                     new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
1188                     AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect
1189             setWiredDeviceConnectionState(
1190                     new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
1191                     AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect
1192         }
1193         mmi.record();
1194     }
1195 
onSaveSetPreferredDevices(int strategy, @NonNull List<AudioDeviceAttributes> devices)1196     /*package*/ void onSaveSetPreferredDevices(int strategy,
1197                                                @NonNull List<AudioDeviceAttributes> devices) {
1198         mPreferredDevices.put(strategy, devices);
1199         List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy);
1200         if (nonDefaultDevices != null) {
1201             nonDefaultDevices.removeAll(devices);
1202 
1203             if (nonDefaultDevices.isEmpty()) {
1204                 mNonDefaultDevices.remove(strategy);
1205             } else {
1206                 mNonDefaultDevices.put(strategy, nonDefaultDevices);
1207             }
1208             dispatchNonDefaultDevice(strategy, nonDefaultDevices);
1209         }
1210 
1211         dispatchPreferredDevice(strategy, devices);
1212     }
1213 
onSaveRemovePreferredDevices(int strategy)1214     /*package*/ void onSaveRemovePreferredDevices(int strategy) {
1215         mPreferredDevices.remove(strategy);
1216         dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>());
1217     }
1218 
onSaveSetDeviceAsNonDefault(int strategy, @NonNull AudioDeviceAttributes device)1219     /*package*/ void onSaveSetDeviceAsNonDefault(int strategy,
1220                                                  @NonNull AudioDeviceAttributes device) {
1221         List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy);
1222         if (nonDefaultDevices == null) {
1223             nonDefaultDevices = new ArrayList<>();
1224         }
1225 
1226         if (!nonDefaultDevices.contains(device)) {
1227             nonDefaultDevices.add(device);
1228         }
1229 
1230         mNonDefaultDevices.put(strategy, nonDefaultDevices);
1231         dispatchNonDefaultDevice(strategy, nonDefaultDevices);
1232 
1233         List<AudioDeviceAttributes> preferredDevices = mPreferredDevices.get(strategy);
1234 
1235         if (preferredDevices != null) {
1236             preferredDevices.remove(device);
1237             mPreferredDevices.put(strategy, preferredDevices);
1238 
1239             dispatchPreferredDevice(strategy, preferredDevices);
1240         }
1241     }
1242 
onSaveRemoveDeviceAsNonDefault(int strategy, @NonNull AudioDeviceAttributes device)1243     /*package*/ void onSaveRemoveDeviceAsNonDefault(int strategy,
1244                                                     @NonNull AudioDeviceAttributes device) {
1245         List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy);
1246         if (nonDefaultDevices != null) {
1247             nonDefaultDevices.remove(device);
1248             mNonDefaultDevices.put(strategy, nonDefaultDevices);
1249             dispatchNonDefaultDevice(strategy, nonDefaultDevices);
1250         }
1251     }
1252 
onSaveSetPreferredDevicesForCapturePreset( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)1253     /*package*/ void onSaveSetPreferredDevicesForCapturePreset(
1254             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
1255         mPreferredDevicesForCapturePreset.put(capturePreset, devices);
1256         dispatchDevicesRoleForCapturePreset(
1257                 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
1258     }
1259 
onSaveClearPreferredDevicesForCapturePreset(int capturePreset)1260     /*package*/ void onSaveClearPreferredDevicesForCapturePreset(int capturePreset) {
1261         mPreferredDevicesForCapturePreset.remove(capturePreset);
1262         dispatchDevicesRoleForCapturePreset(
1263                 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED,
1264                 new ArrayList<AudioDeviceAttributes>());
1265     }
1266 
1267     //------------------------------------------------------------
1268     // preferred/non-default device(s)
1269 
setPreferredDevicesForStrategyAndSave(int strategy, @NonNull List<AudioDeviceAttributes> devices)1270     /*package*/ int setPreferredDevicesForStrategyAndSave(int strategy,
1271             @NonNull List<AudioDeviceAttributes> devices) {
1272         final int status = setPreferredDevicesForStrategy(strategy, devices);
1273         if (status == AudioSystem.SUCCESS) {
1274             mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
1275         }
1276         return status;
1277     }
1278     // Only used for external requests coming from an API
setPreferredDevicesForStrategy(int strategy, @NonNull List<AudioDeviceAttributes> devices)1279     /*package*/ int setPreferredDevicesForStrategy(int strategy,
1280             @NonNull List<AudioDeviceAttributes> devices) {
1281         int status = AudioSystem.ERROR;
1282         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1283             status = setDevicesRoleForStrategy(
1284                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */);
1285         }
1286         return status;
1287     }
1288     // Only used for internal requests
setPreferredDevicesForStrategyInt(int strategy, @NonNull List<AudioDeviceAttributes> devices)1289     /*package*/ int setPreferredDevicesForStrategyInt(int strategy,
1290                                                   @NonNull List<AudioDeviceAttributes> devices) {
1291 
1292         return setDevicesRoleForStrategy(
1293                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, true /* internal */);
1294     }
1295 
removePreferredDevicesForStrategyAndSave(int strategy)1296     /*package*/ int removePreferredDevicesForStrategyAndSave(int strategy) {
1297         final int status = removePreferredDevicesForStrategy(strategy);
1298         if (status == AudioSystem.SUCCESS) {
1299             mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
1300         }
1301         return status;
1302     }
1303     // Only used for external requests coming from an API
removePreferredDevicesForStrategy(int strategy)1304     /*package*/ int removePreferredDevicesForStrategy(int strategy) {
1305         int status = AudioSystem.ERROR;
1306 
1307         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1308             status = clearDevicesRoleForStrategy(
1309                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */);
1310         }
1311         return status;
1312     }
1313     // Only used for internal requests
removePreferredDevicesForStrategyInt(int strategy)1314     /*package*/ int removePreferredDevicesForStrategyInt(int strategy) {
1315         return clearDevicesRoleForStrategy(
1316                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED, true /*internal */);
1317     }
1318 
setDeviceAsNonDefaultForStrategyAndSave(int strategy, @NonNull AudioDeviceAttributes device)1319     /*package*/ int setDeviceAsNonDefaultForStrategyAndSave(int strategy,
1320             @NonNull AudioDeviceAttributes device) {
1321         int status = AudioSystem.ERROR;
1322 
1323         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1324             List<AudioDeviceAttributes> devices = new ArrayList<>();
1325             devices.add(device);
1326             status = addDevicesRoleForStrategy(
1327                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
1328         }
1329 
1330         if (status == AudioSystem.SUCCESS) {
1331             mDeviceBroker.postSaveSetDeviceAsNonDefaultForStrategy(strategy, device);
1332         }
1333         return status;
1334     }
1335 
removeDeviceAsNonDefaultForStrategyAndSave(int strategy, @NonNull AudioDeviceAttributes device)1336     /*package*/ int removeDeviceAsNonDefaultForStrategyAndSave(int strategy,
1337             @NonNull AudioDeviceAttributes device) {
1338         int status = AudioSystem.ERROR;
1339 
1340         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1341             List<AudioDeviceAttributes> devices = new ArrayList<>();
1342             devices.add(device);
1343             status = removeDevicesRoleForStrategy(
1344                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
1345         }
1346 
1347         if (status == AudioSystem.SUCCESS) {
1348             mDeviceBroker.postSaveRemoveDeviceAsNonDefaultForStrategy(strategy, device);
1349         }
1350         return status;
1351     }
1352 
1353 
registerStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged)1354     /*package*/ void registerStrategyPreferredDevicesDispatcher(
1355             @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
1356         mPrefDevDispatchers.register(dispatcher, isPrivileged);
1357     }
1358 
unregisterStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)1359     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
1360             @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
1361         mPrefDevDispatchers.unregister(dispatcher);
1362     }
1363 
registerStrategyNonDefaultDevicesDispatcher( @onNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged)1364     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
1365             @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
1366         mNonDefDevDispatchers.register(dispatcher, isPrivileged);
1367     }
1368 
unregisterStrategyNonDefaultDevicesDispatcher( @onNull IStrategyNonDefaultDevicesDispatcher dispatcher)1369     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
1370             @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
1371         mNonDefDevDispatchers.unregister(dispatcher);
1372     }
1373 
setPreferredDevicesForCapturePresetAndSave( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)1374     /*package*/ int setPreferredDevicesForCapturePresetAndSave(
1375             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
1376         final int status = setPreferredDevicesForCapturePreset(capturePreset, devices);
1377         if (status == AudioSystem.SUCCESS) {
1378             mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
1379         }
1380         return status;
1381     }
1382 
1383     // Only used for external requests coming from an API
setPreferredDevicesForCapturePreset( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)1384     private int setPreferredDevicesForCapturePreset(
1385             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
1386         int status = AudioSystem.ERROR;
1387         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1388             status = setDevicesRoleForCapturePreset(
1389                     capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
1390         }
1391         return status;
1392     }
1393 
clearPreferredDevicesForCapturePresetAndSave(int capturePreset)1394     /*package*/ int clearPreferredDevicesForCapturePresetAndSave(int capturePreset) {
1395         final int status  = clearPreferredDevicesForCapturePreset(capturePreset);
1396         if (status == AudioSystem.SUCCESS) {
1397             mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
1398         }
1399         return status;
1400     }
1401 
1402     // Only used for external requests coming from an API
clearPreferredDevicesForCapturePreset(int capturePreset)1403     private int clearPreferredDevicesForCapturePreset(int capturePreset) {
1404         int status  = AudioSystem.ERROR;
1405 
1406         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1407             status = clearDevicesRoleForCapturePreset(
1408                     capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
1409         }
1410         return status;
1411     }
1412 
1413     // Only used for internal requests
addDevicesRoleForCapturePresetInt(int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)1414     private int addDevicesRoleForCapturePresetInt(int capturePreset, int role,
1415                                                @NonNull List<AudioDeviceAttributes> devices) {
1416         return addDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> {
1417             return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
1418         }, capturePreset, role, devices);
1419     }
1420 
1421     // Only used for internal requests
removeDevicesRoleForCapturePresetInt(int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)1422     private int removeDevicesRoleForCapturePresetInt(int capturePreset, int role,
1423                                                   @NonNull List<AudioDeviceAttributes> devices) {
1424         return removeDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> {
1425             return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d);
1426         }, capturePreset, role, devices);
1427     }
1428 
1429     // Only used for external requests coming from an API
1430     private int setDevicesRoleForCapturePreset(int capturePreset, int role,
1431                                                @NonNull List<AudioDeviceAttributes> devices) {
1432         return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
1433             return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
1434         }, (p, r, d) -> {
1435                 return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
1436             }, capturePreset, role, devices);
1437     }
1438 
1439     // Only used for external requests coming from an API
1440     private int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
1441         return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
1442             return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
1443         }, capturePreset, role);
1444     }
1445 
1446     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
1447             @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
1448         mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
1449     }
1450 
1451     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
1452             @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
1453         mDevRoleCapturePresetDispatchers.unregister(dispatcher);
1454     }
1455 
1456     private int addDevicesRoleForStrategy(int strategy, int role,
1457                                           @NonNull List<AudioDeviceAttributes> devices,
1458                                           boolean internal) {
1459         return addDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
1460                 (s, r, d) -> {
1461                     return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
1462                 }, strategy, role, devices);
1463     }
1464 
1465     private int removeDevicesRoleForStrategy(int strategy, int role,
1466                                       @NonNull List<AudioDeviceAttributes> devices,
1467                                              boolean internal) {
1468         return removeDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
1469                 (s, r, d) -> {
1470                     return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
1471                 }, strategy, role, devices);
1472     }
1473 
1474     private int setDevicesRoleForStrategy(int strategy, int role,
1475                                           @NonNull List<AudioDeviceAttributes> devices,
1476                                           boolean internal) {
1477         return setDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
1478                 (s, r, d) -> {
1479                     return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
1480                 }, (s, r, d) -> {
1481                     return mAudioSystem.clearDevicesRoleForStrategy(s, r);
1482                 }, strategy, role, devices);
1483     }
1484 
1485     private int clearDevicesRoleForStrategy(int strategy, int role, boolean internal) {
1486         return clearDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
1487                 (s, r, d) -> {
1488                     return mAudioSystem.clearDevicesRoleForStrategy(s, r);
1489                 }, strategy, role);
1490     }
1491 
1492     //------------------------------------------------------------
1493     // Cache for applied roles for strategies and devices. The cache avoids reapplying the
1494     // same list of devices for a given role and strategy and the corresponding systematic
1495     // redundant work in audio policy manager and audio flinger.
1496     // The key is the pair <Strategy , Role> and the value is the current list of devices.
1497     // mAppliedStrategyRoles is for requests coming from an API.
1498     // mAppliedStrategyRolesInt is for internal requests. Entries are removed when the requested
1499     // device is disconnected.
1500     private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
1501             mAppliedStrategyRoles = new ArrayMap<>();
1502     private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
1503             mAppliedStrategyRolesInt = new ArrayMap<>();
1504 
1505     // Cache for applied roles for capture presets and devices. The cache avoids reapplying the
1506     // same list of devices for a given role and capture preset and the corresponding systematic
1507     // redundant work in audio policy manager and audio flinger.
1508     // The key is the pair <Preset , Role> and the value is the current list of devices.
1509     // mAppliedPresetRoles is for requests coming from an API.
1510     // mAppliedPresetRolesInt is for internal requests. Entries are removed when the requested
1511     // device is disconnected.
1512     private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
1513             mAppliedPresetRoles = new ArrayMap<>();
1514     private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
1515             mAppliedPresetRolesInt = new ArrayMap<>();
1516 
1517     interface AudioSystemInterface {
1518         int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices);
1519     }
1520 
1521     private int addDevicesRole(
1522             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
1523             AudioSystemInterface asi,
1524             int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
1525         synchronized (rolesMap) {
1526             Pair<Integer, Integer> key = new Pair<>(useCase, role);
1527             List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
1528             List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
1529 
1530             if (rolesMap.containsKey(key)) {
1531                 roleDevices = rolesMap.get(key);
1532                 for (AudioDeviceAttributes device : devices) {
1533                     if (!roleDevices.contains(device)) {
1534                         appliedDevices.add(device);
1535                     }
1536                 }
1537             } else {
1538                 appliedDevices.addAll(devices);
1539             }
1540             if (appliedDevices.isEmpty()) {
1541                 return AudioSystem.SUCCESS;
1542             }
1543             final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
1544             if (status == AudioSystem.SUCCESS) {
1545                 roleDevices.addAll(appliedDevices);
1546                 rolesMap.put(key, roleDevices);
1547             }
1548             return status;
1549         }
1550     }
1551 
1552     private int removeDevicesRole(
1553             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
1554             AudioSystemInterface asi,
1555             int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
1556         synchronized (rolesMap) {
1557             Pair<Integer, Integer> key = new Pair<>(useCase, role);
1558             if (!rolesMap.containsKey(key)) {
1559                 // trying to remove a role for a device that wasn't set
1560                 return AudioSystem.BAD_VALUE;
1561             }
1562             List<AudioDeviceAttributes> roleDevices = rolesMap.get(key);
1563             List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
1564             for (AudioDeviceAttributes device : devices) {
1565                 if (roleDevices.contains(device)) {
1566                     appliedDevices.add(device);
1567                 }
1568             }
1569             if (appliedDevices.isEmpty()) {
1570                 return AudioSystem.SUCCESS;
1571             }
1572             final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
1573             if (status == AudioSystem.SUCCESS) {
1574                 roleDevices.removeAll(appliedDevices);
1575                 if (roleDevices.isEmpty()) {
1576                     rolesMap.remove(key);
1577                 } else {
1578                     rolesMap.put(key, roleDevices);
1579                 }
1580             }
1581             return status;
1582         }
1583     }
1584 
1585     private static boolean devicesListEqual(@NonNull List<AudioDeviceAttributes> list1,
1586                                             @NonNull List<AudioDeviceAttributes> list2) {
1587         if (list1.size() != list2.size()) {
1588             return false;
1589         }
1590         // This assumes a given device is only present once in a list
1591         for (AudioDeviceAttributes d1 : list1) {
1592             boolean found = false;
1593             for (AudioDeviceAttributes d2 : list2) {
1594                 if (d1.equalTypeAddress(d2)) {
1595                     found = true;
1596                     break;
1597                 }
1598             }
1599             if (!found) {
1600                 return false;
1601             }
1602         }
1603         return true;
1604     }
1605 
1606     private int setDevicesRole(
1607             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
1608             AudioSystemInterface addOp,
1609             AudioSystemInterface clearOp,
1610             int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
1611         synchronized (rolesMap) {
1612             Pair<Integer, Integer> key = new Pair<>(useCase, role);
1613             if (rolesMap.containsKey(key)) {
1614                 if (devicesListEqual(devices, rolesMap.get(key))) {
1615                     // NO OP: no change in preference
1616                     return AudioSystem.SUCCESS;
1617                 }
1618             } else if (devices.isEmpty()) {
1619                 // NO OP: no preference to no preference
1620                 return AudioSystem.SUCCESS;
1621             }
1622             int status;
1623             if (devices.isEmpty()) {
1624                 status = clearOp.deviceRoleAction(useCase, role, null);
1625                 if (status == AudioSystem.SUCCESS) {
1626                     rolesMap.remove(key);
1627                 }
1628             } else {
1629                 status = addOp.deviceRoleAction(useCase, role, devices);
1630                 if (status == AudioSystem.SUCCESS) {
1631                     rolesMap.put(key, new ArrayList(devices));
1632                 }
1633             }
1634             return status;
1635         }
1636     }
1637 
1638     private int clearDevicesRole(
1639             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
1640             AudioSystemInterface asi, int useCase, int role) {
1641         synchronized (rolesMap) {
1642             Pair<Integer, Integer> key = new Pair<>(useCase, role);
1643             if (!rolesMap.containsKey(key)) {
1644                 // trying to clear a role for a device that wasn't set
1645                 return AudioSystem.BAD_VALUE;
1646             }
1647             final int status = asi.deviceRoleAction(useCase, role, null);
1648             if (status == AudioSystem.SUCCESS) {
1649                 rolesMap.remove(key);
1650             }
1651             return status;
1652         }
1653     }
1654 
1655     @GuardedBy("mDevicesLock")
1656     private void purgeDevicesRoles_l() {
1657         purgeRoles(mAppliedStrategyRolesInt, (s, r, d) -> {
1658             return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
1659         purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> {
1660             return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
1661         reapplyExternalDevicesRoles();
1662     }
1663 
1664     @GuardedBy("mDevicesLock")
1665     private void purgeRoles(
1666             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
1667             AudioSystemInterface asi) {
1668         synchronized (rolesMap) {
1669             AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic(
1670                     AudioManager.GET_DEVICES_ALL);
1671 
1672             Iterator<Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
1673                     rolesMap.entrySet().iterator();
1674 
1675             while (itRole.hasNext()) {
1676                 Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
1677                         itRole.next();
1678                 Pair<Integer, Integer> keyRole = entry.getKey();
1679                 Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
1680                 while (itDev.hasNext()) {
1681                     AudioDeviceAttributes ada = itDev.next();
1682 
1683                     AudioDeviceInfo device = Stream.of(connectedDevices)
1684                             .filter(d -> d.getInternalType() == ada.getInternalType())
1685                             .filter(d -> (!isBluetoothDevice(d.getInternalType())
1686                                             || (d.getAddress().equals(ada.getAddress()))))
1687                             .findFirst()
1688                             .orElse(null);
1689 
1690                     if (device == null) {
1691                         if (AudioService.DEBUG_DEVICES) {
1692                             Slog.i(TAG, "purgeRoles() removing device: " + ada.toString()
1693                                     + ", for strategy: " + keyRole.first
1694                                     + " and role: " + keyRole.second);
1695                         }
1696                         asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada));
1697                         itDev.remove();
1698                     }
1699                 }
1700                 if (rolesMap.get(keyRole).isEmpty()) {
1701                     itRole.remove();
1702                 }
1703             }
1704         }
1705     }
1706 
1707 //-----------------------------------------------------------------------
1708 
1709     /**
1710      * Check if a device is in the list of connected devices
1711      * @param device the device whose connection state is queried
1712      * @return true if connected
1713      */
1714     @GuardedBy("mDeviceBroker.mDeviceStateLock")
1715     public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
1716         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
1717                 device.getAddress());
1718         synchronized (mDevicesLock) {
1719             return (mConnectedDevices.get(key) != null);
1720         }
1721     }
1722 
1723     /**
1724      * Implements the communication with AudioSystem to (dis)connect a device in the native layers
1725      * @param attributes the attributes of the device
1726      * @param connect true if connection
1727      * @param isForTesting if true, not calling AudioSystem for the connection as this is
1728      *                    just for testing
1729      * @param btDevice the corresponding Bluetooth device when relevant.
1730      * @return false if an error was reported by AudioSystem
1731      */
1732     /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
1733                                                boolean connect, boolean isForTesting,
1734                                                @Nullable BluetoothDevice btDevice) {
1735         int device = attributes.getInternalType();
1736         String address = attributes.getAddress();
1737         String deviceName = attributes.getName();
1738         if (AudioService.DEBUG_DEVICES) {
1739             Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
1740                     + Integer.toHexString(device) + " address:" + address
1741                     + " name:" + deviceName + ")");
1742         }
1743         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection")
1744                 .set(MediaMetrics.Property.ADDRESS, address)
1745                 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device))
1746                 .set(MediaMetrics.Property.MODE, connect
1747                         ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
1748                 .set(MediaMetrics.Property.NAME, deviceName);
1749         boolean status = false;
1750         synchronized (mDevicesLock) {
1751             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
1752             if (AudioService.DEBUG_DEVICES) {
1753                 Slog.i(TAG, "deviceKey:" + deviceKey);
1754             }
1755             DeviceInfo di = mConnectedDevices.get(deviceKey);
1756             boolean isConnected = di != null;
1757             if (AudioService.DEBUG_DEVICES) {
1758                 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
1759             }
1760             if (connect && !isConnected) {
1761                 final int res;
1762                 if (isForTesting) {
1763                     res = AudioSystem.AUDIO_STATUS_OK;
1764                 } else {
1765                     res = mAudioSystem.setDeviceConnectionState(attributes,
1766                             AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
1767                 }
1768                 if (res != AudioSystem.AUDIO_STATUS_OK) {
1769                     final String reason = "not connecting device 0x" + Integer.toHexString(device)
1770                             + " due to command error " + res;
1771                     mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
1772                             .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED)
1773                             .record();
1774                     AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
1775                             "APM failed to make available device 0x" + Integer.toHexString(device)
1776                             + "addr=" + address + " error=" + res)
1777                             .printSlog(EventLogger.Event.ALOGE, TAG));
1778                     return false;
1779                 }
1780                 mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
1781                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
1782                 status = true;
1783             } else if (!connect && isConnected) {
1784                 mAudioSystem.setDeviceConnectionState(attributes,
1785                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
1786                 // always remove even if disconnection failed
1787                 mConnectedDevices.remove(deviceKey);
1788                 mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
1789                 status = true;
1790             }
1791             if (status) {
1792                 if (AudioSystem.isBluetoothScoDevice(device)) {
1793                     updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
1794                     if (!connect) {
1795                         purgeDevicesRoles_l();
1796                     } else {
1797                         addAudioDeviceInInventoryIfNeeded(device, address, "",
1798                                 BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
1799                     }
1800                     AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
1801                             "SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
1802                             + " device addr=" + address
1803                             + (connect ? " now available" : " made unavailable"))
1804                             .printSlog(EventLogger.Event.ALOGI, TAG));
1805                 }
1806                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
1807             } else {
1808                 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
1809                         + ", deviceSpec=" + di + ", connect=" + connect);
1810                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
1811             }
1812         }
1813         return status;
1814     }
1815 
1816 
1817     private void disconnectA2dp() {
1818         synchronized (mDevicesLock) {
1819             final ArraySet<String> toRemove = new ArraySet<>();
1820             // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
1821             mConnectedDevices.values().forEach(deviceInfo -> {
1822                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
1823                     toRemove.add(deviceInfo.mDeviceAddress);
1824                 }
1825             });
1826             new MediaMetrics.Item(mMetricsId + "disconnectA2dp")
1827                     .set(MediaMetrics.Property.EVENT, "disconnectA2dp")
1828                     .record();
1829             if (toRemove.size() > 0) {
1830                 final int delay = checkSendBecomingNoisyIntentInt(
1831                         AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
1832                         AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
1833                 toRemove.stream().forEach(deviceAddress ->
1834                         makeA2dpDeviceUnavailableLater(deviceAddress, delay)
1835                 );
1836             }
1837         }
1838     }
1839 
1840     private void disconnectA2dpSink() {
1841         synchronized (mDevicesLock) {
1842             final ArraySet<String> toRemove = new ArraySet<>();
1843             // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
1844             mConnectedDevices.values().forEach(deviceInfo -> {
1845                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
1846                     toRemove.add(deviceInfo.mDeviceAddress);
1847                 }
1848             });
1849             new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink")
1850                     .set(MediaMetrics.Property.EVENT, "disconnectA2dpSink")
1851                     .record();
1852             toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
1853         }
1854     }
1855 
1856     private void disconnectHearingAid() {
1857         synchronized (mDevicesLock) {
1858             final ArraySet<String> toRemove = new ArraySet<>();
1859             // Disconnect ALL DEVICE_OUT_HEARING_AID devices
1860             mConnectedDevices.values().forEach(deviceInfo -> {
1861                 if (deviceInfo.mDeviceType == DEVICE_OUT_HEARING_AID) {
1862                     toRemove.add(deviceInfo.mDeviceAddress);
1863                 }
1864             });
1865             new MediaMetrics.Item(mMetricsId + "disconnectHearingAid")
1866                     .set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
1867                     .record();
1868             if (toRemove.size() > 0) {
1869                 /*final int delay = */
1870                 checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
1871                         AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
1872                 toRemove.stream().forEach(deviceAddress ->
1873                         // TODO delay not used?
1874                         makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
1875                 );
1876             }
1877         }
1878     }
1879 
1880     @GuardedBy("mDeviceBroker.mDeviceStateLock")
1881     /*package*/ void onBtProfileDisconnected(int profile) {
1882         switch (profile) {
1883             case BluetoothProfile.HEADSET:
1884                 disconnectHeadset();
1885                 break;
1886             case BluetoothProfile.A2DP:
1887                 disconnectA2dp();
1888                 break;
1889             case BluetoothProfile.A2DP_SINK:
1890                 disconnectA2dpSink();
1891                 break;
1892             case BluetoothProfile.HEARING_AID:
1893                 disconnectHearingAid();
1894                 break;
1895             case BluetoothProfile.LE_AUDIO:
1896                 disconnectLeAudioUnicast();
1897                 break;
1898             case BluetoothProfile.LE_AUDIO_BROADCAST:
1899                 disconnectLeAudioBroadcast();
1900                 break;
1901             default:
1902                 // Not a valid profile to disconnect
1903                 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
1904                         + BluetoothProfile.getProfileName(profile));
1905                 break;
1906         }
1907     }
1908 
1909      /*package*/ void disconnectLeAudio(int device) {
1910         if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET
1911                 && device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) {
1912             Log.e(TAG, "disconnectLeAudio: Can't disconnect not LE Audio device " + device);
1913             return;
1914         }
1915 
1916         synchronized (mDevicesLock) {
1917             final ArraySet<Pair<String, Integer>> toRemove = new ArraySet<>();
1918             // Disconnect ALL DEVICE_OUT_BLE_HEADSET or DEVICE_OUT_BLE_BROADCAST devices
1919             mConnectedDevices.values().forEach(deviceInfo -> {
1920                 if (deviceInfo.mDeviceType == device) {
1921                     toRemove.add(
1922                             new Pair<>(deviceInfo.mDeviceAddress, deviceInfo.mDeviceCodecFormat));
1923                 }
1924             });
1925             new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
1926                     .set(MediaMetrics.Property.EVENT, "disconnectLeAudio")
1927                     .record();
1928             if (toRemove.size() > 0) {
1929                 final int delay = checkSendBecomingNoisyIntentInt(device,
1930                         AudioService.CONNECTION_STATE_DISCONNECTED,
1931                         AudioSystem.DEVICE_NONE);
1932                 toRemove.stream().forEach(entry ->
1933                         makeLeAudioDeviceUnavailableLater(entry.first, device, entry.second, delay)
1934                 );
1935             }
1936         }
1937     }
1938 
1939     /*package*/ void disconnectLeAudioUnicast() {
1940         disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_HEADSET);
1941     }
1942 
1943     /*package*/ void disconnectLeAudioBroadcast() {
1944         disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
1945     }
1946 
1947     @GuardedBy("mDeviceBroker.mDeviceStateLock")
1948     private void disconnectHeadset() {
1949         boolean disconnect = false;
1950         synchronized (mDevicesLock) {
1951             for (DeviceInfo di : mConnectedDevices.values()) {
1952                 if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
1953                     // There is only one HFP active device and setting the active
1954                     // device to null will disconnect both in and out devices
1955                     disconnect = true;
1956                     break;
1957                 }
1958             }
1959         }
1960         if (disconnect) {
1961             mDeviceBroker.onSetBtScoActiveDevice(null);
1962         }
1963     }
1964 
1965     // must be called before removing the device from mConnectedDevices
1966     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
1967     // from AudioSystem
1968     /*package*/ int checkSendBecomingNoisyIntent(int device,
1969             @AudioService.ConnectionState int state, int musicDevice) {
1970         synchronized (mDevicesLock) {
1971             return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
1972         }
1973     }
1974 
1975     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
1976         synchronized (mCurAudioRoutes) {
1977             AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
1978             mRoutesObservers.register(observer);
1979             return routes;
1980         }
1981     }
1982 
1983     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
1984         return mCurAudioRoutes;
1985     }
1986 
1987     /**
1988      * Set a Bluetooth device to active.
1989      */
1990     @GuardedBy("mDeviceBroker.mDeviceStateLock")
1991     public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) {
1992         int delay;
1993         synchronized (mDevicesLock) {
1994             if (!info.mSupprNoisy
1995                     && (((info.mProfile == BluetoothProfile.LE_AUDIO
1996                         || info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST)
1997                         && info.mIsLeOutput)
1998                         || info.mProfile == BluetoothProfile.HEARING_AID
1999                         || info.mProfile == BluetoothProfile.A2DP)) {
2000                 @AudioService.ConnectionState int asState =
2001                         (info.mState == BluetoothProfile.STATE_CONNECTED)
2002                                 ? AudioService.CONNECTION_STATE_CONNECTED
2003                                 : AudioService.CONNECTION_STATE_DISCONNECTED;
2004                 delay = checkSendBecomingNoisyIntentInt(info.mAudioSystemDevice, asState,
2005                         info.mMusicDevice);
2006             } else {
2007                 delay = 0;
2008             }
2009 
2010             if (AudioService.DEBUG_DEVICES) {
2011                 Log.i(TAG, "setBluetoothActiveDevice " + info.toString() + " delay(ms): " + delay);
2012             }
2013             mDeviceBroker.postBluetoothActiveDevice(info, delay);
2014         }
2015         return delay;
2016     }
2017 
2018     /*package*/ int setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
2019             @AudioService.ConnectionState int state, String caller) {
2020         synchronized (mDevicesLock) {
2021             int delay = checkSendBecomingNoisyIntentInt(
2022                     attributes.getInternalType(), state, AudioSystem.DEVICE_NONE);
2023             mDeviceBroker.postSetWiredDeviceConnectionState(
2024                     new WiredDeviceConnectionState(attributes, state, caller), delay);
2025             return delay;
2026         }
2027     }
2028 
2029     /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
2030             @AudioService.ConnectionState int state) {
2031         final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
2032                 device, state, "com.android.server.audio");
2033         connection.mForTest = true;
2034         onSetWiredDeviceConnectionState(connection);
2035     }
2036 
2037     //-------------------------------------------------------------------
2038     // Internal utilities
2039 
2040     @GuardedBy("mDevicesLock")
2041     private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
2042                                          @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
2043                                          String eventSource) {
2044         final String address = btInfo.mDevice.getAddress();
2045         final String name = BtHelper.getName(btInfo.mDevice);
2046 
2047         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
2048         // audio policy manager
2049         mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
2050         // at this point there could be another A2DP device already connected in APM, but it
2051         // doesn't matter as this new one will overwrite the previous one
2052         AudioDeviceAttributes ada = new AudioDeviceAttributes(
2053                 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
2054         final int res = mAudioSystem.setDeviceConnectionState(ada,
2055                 AudioSystem.DEVICE_STATE_AVAILABLE, codec);
2056 
2057         // TODO: log in MediaMetrics once distinction between connection failure and
2058         // double connection is made.
2059         if (res != AudioSystem.AUDIO_STATUS_OK) {
2060             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2061                     "APM failed to make available A2DP device addr="
2062                             + Utils.anonymizeBluetoothAddress(address)
2063                             + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
2064             // TODO: connection failed, stop here
2065             // TODO: return;
2066         } else {
2067             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2068                     "A2DP sink device addr=" + Utils.anonymizeBluetoothAddress(address)
2069                             + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
2070         }
2071 
2072         // Reset A2DP suspend state each time a new sink is connected
2073         mDeviceBroker.clearA2dpSuspended(true /* internalOnly */);
2074 
2075         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
2076                 address, btInfo.mDevice.getIdentityAddress(), codec);
2077         final String diKey = di.getKey();
2078         mConnectedDevices.put(diKey, di);
2079         // on a connection always overwrite the device seen by AudioPolicy, see comment above when
2080         // calling AudioSystem
2081         mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey);
2082 
2083         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
2084         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
2085 
2086         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
2087 
2088         addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
2089                 BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
2090     }
2091 
2092     static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
2093             AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION,
2094             AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD};
2095 
2096     // reflects system property persist.bluetooth.enable_dual_mode_audio
2097     final boolean mBluetoothDualModeEnabled;
2098     /**
2099      * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED
2100      * or not according to their own and other devices modes.
2101      * The top priority is given to LE devices, then SCO ,then A2DP.
2102      */
2103     @GuardedBy("mDevicesLock")
2104     private void applyConnectedDevicesRoles_l() {
2105         if (!mBluetoothDualModeEnabled) {
2106             return;
2107         }
2108         DeviceInfo leOutDevice =
2109                 getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_BLE_SET);
2110         DeviceInfo leInDevice =
2111                 getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
2112         DeviceInfo a2dpDevice =
2113                 getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_A2DP_SET);
2114         DeviceInfo scoOutDevice =
2115                 getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_SCO_SET);
2116         DeviceInfo scoInDevice =
2117                 getFirstConnectedDeviceOfTypes(DEVICE_IN_ALL_SCO_SET);
2118         boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
2119         boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
2120                 || (leInDevice != null && leInDevice.isDuplexModeEnabled());
2121         AudioDeviceAttributes communicationDevice =
2122                 mDeviceBroker.mActiveCommunicationDevice == null
2123                         ? null : ((mDeviceBroker.isInCommunication()
2124                                     && mDeviceBroker.mActiveCommunicationDevice != null)
2125                             ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice)
2126                             : null);
2127 
2128         if (AudioService.DEBUG_DEVICES) {
2129             Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice
2130                     + "\n - leInDevice: " + leInDevice
2131                     + "\n - a2dpDevice: " + a2dpDevice
2132                     + "\n - scoOutDevice: " + scoOutDevice
2133                     + "\n - scoInDevice: " + scoInDevice
2134                     + "\n - disableA2dp: " + disableA2dp
2135                     + ", disableSco: " + disableSco);
2136         }
2137 
2138         for (DeviceInfo di : mConnectedDevices.values()) {
2139             if (!isBluetoothDevice(di.mDeviceType)) {
2140                 continue;
2141             }
2142             AudioDeviceAttributes ada =
2143                     new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
2144             if (AudioService.DEBUG_DEVICES) {
2145                 Log.i(TAG, "  + checking Device: " + ada);
2146             }
2147             if (ada.equalTypeAddress(communicationDevice)) {
2148                 continue;
2149             }
2150 
2151             if (isBluetoothOutDevice(di.mDeviceType)) {
2152                 for (AudioProductStrategy strategy : mStrategies) {
2153                     boolean disable = false;
2154                     if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
2155                         if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
2156                             disable = disableSco || !di.isDuplexModeEnabled();
2157                         } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
2158                             disable = !di.isDuplexModeEnabled();
2159                         }
2160                     } else {
2161                         if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) {
2162                             disable = disableA2dp || !di.isOutputOnlyModeEnabled();
2163                         } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
2164                             disable = disableSco || !di.isOutputOnlyModeEnabled();
2165                         } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
2166                             disable = !di.isOutputOnlyModeEnabled();
2167                         }
2168                     }
2169                     if (AudioService.DEBUG_DEVICES) {
2170                         Log.i(TAG, "     - strategy: " + strategy.getId()
2171                                 + ", disable: " + disable);
2172                     }
2173                     if (disable) {
2174                         addDevicesRoleForStrategy(strategy.getId(),
2175                                 AudioSystem.DEVICE_ROLE_DISABLED,
2176                                 Arrays.asList(ada), true /* internal */);
2177                     } else {
2178                         removeDevicesRoleForStrategy(strategy.getId(),
2179                                 AudioSystem.DEVICE_ROLE_DISABLED,
2180                                 Arrays.asList(ada), true /* internal */);
2181                     }
2182                 }
2183             }
2184             if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) {
2185                 for (int capturePreset : CAPTURE_PRESETS) {
2186                     boolean disable = false;
2187                     if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
2188                         disable = disableSco || !di.isDuplexModeEnabled();
2189                     } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
2190                         disable = !di.isDuplexModeEnabled();
2191                     }
2192                     if (AudioService.DEBUG_DEVICES) {
2193                         Log.i(TAG, "      - capturePreset: " + capturePreset
2194                                 + ", disable: " + disable);
2195                     }
2196                     if (disable) {
2197                         addDevicesRoleForCapturePresetInt(capturePreset,
2198                                 AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
2199                     } else {
2200                         removeDevicesRoleForCapturePresetInt(capturePreset,
2201                                 AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
2202                     }
2203                 }
2204             }
2205         }
2206     }
2207 
2208     /* package */ void applyConnectedDevicesRoles() {
2209         synchronized (mDevicesLock) {
2210             applyConnectedDevicesRoles_l();
2211         }
2212     }
2213 
2214     @GuardedBy("mDevicesLock")
2215     int checkProfileIsConnected(int profile) {
2216         switch (profile) {
2217             case BluetoothProfile.HEADSET:
2218                 if (getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_SCO_SET) != null
2219                         || getFirstConnectedDeviceOfTypes(DEVICE_IN_ALL_SCO_SET) != null) {
2220                     return profile;
2221                 }
2222                 break;
2223             case BluetoothProfile.A2DP:
2224                 if (getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_A2DP_SET) != null) {
2225                     return profile;
2226                 }
2227                 break;
2228             case BluetoothProfile.LE_AUDIO:
2229             case BluetoothProfile.LE_AUDIO_BROADCAST:
2230                 if (getFirstConnectedDeviceOfTypes(
2231                         DEVICE_OUT_ALL_BLE_SET) != null
2232                         || getFirstConnectedDeviceOfTypes(
2233                                 AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
2234                     return profile;
2235                 }
2236                 break;
2237             default:
2238                 break;
2239         }
2240         return 0;
2241     }
2242 
2243     @GuardedBy("mDevicesLock")
2244     private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) {
2245         if (!mBluetoothDualModeEnabled) {
2246             return;
2247         }
2248         HashSet<String> processedAddresses = new HashSet<>(0);
2249         for (DeviceInfo di : mConnectedDevices.values()) {
2250             if (!isBluetoothDevice(di.mDeviceType)
2251                     || processedAddresses.contains(di.mDeviceAddress)) {
2252                 continue;
2253             }
2254             Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress);
2255             if (AudioService.DEBUG_DEVICES) {
2256                 Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: "
2257                         + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
2258             }
2259             for (DeviceInfo di2 : mConnectedDevices.values()) {
2260                 if (!isBluetoothDevice(di2.mDeviceType)
2261                         || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
2262                     continue;
2263                 }
2264                 int profile = BtHelper.getProfileFromType(di2.mDeviceType);
2265                 if (profile == 0) {
2266                     continue;
2267                 }
2268                 int preferredProfile = checkProfileIsConnected(
2269                         preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
2270                 if (preferredProfile == profile || preferredProfile == 0) {
2271                     di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
2272                 } else {
2273                     di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
2274                 }
2275                 preferredProfile = checkProfileIsConnected(
2276                         preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY));
2277                 if (preferredProfile == profile || preferredProfile == 0) {
2278                     di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
2279                 } else {
2280                     di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
2281                 }
2282             }
2283             processedAddresses.add(di.mDeviceAddress);
2284         }
2285         applyConnectedDevicesRoles_l();
2286         if (connectedDevice != null) {
2287             mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice);
2288         }
2289     }
2290 
2291     @GuardedBy("mDevicesLock")
2292     private void makeA2dpDeviceUnavailableNow(String address, int codec) {
2293         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
2294                 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
2295                 .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
2296 
2297         if (address == null) {
2298             mmi.set(MediaMetrics.Property.EARLY_RETURN, "address null").record();
2299             return;
2300         }
2301         final String deviceToRemoveKey =
2302                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
2303 
2304         mConnectedDevices.remove(deviceToRemoveKey);
2305         if (!deviceToRemoveKey
2306                 .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
2307             // removing A2DP device not currently used by AudioPolicy, log but don't act on it
2308             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
2309                     "A2DP device " + Utils.anonymizeBluetoothAddress(address)
2310                             + " made unavailable, was not used"))
2311                     .printSlog(EventLogger.Event.ALOGI, TAG));
2312             mmi.set(MediaMetrics.Property.EARLY_RETURN,
2313                     "A2DP device made unavailable, was not used")
2314                     .record();
2315             return;
2316         }
2317 
2318         // device to remove was visible by APM, update APM
2319         mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
2320         AudioDeviceAttributes ada = new AudioDeviceAttributes(
2321                 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
2322         final int res = mAudioSystem.setDeviceConnectionState(ada,
2323                 AudioSystem.DEVICE_STATE_UNAVAILABLE, codec);
2324 
2325         if (res != AudioSystem.AUDIO_STATUS_OK) {
2326             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2327                     "APM failed to make unavailable A2DP device addr="
2328                             + Utils.anonymizeBluetoothAddress(address)
2329                             + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
2330             // TODO:  failed to disconnect, stop here
2331             // TODO: return;
2332         } else {
2333             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
2334                     "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
2335                             + " made unavailable")).printSlog(EventLogger.Event.ALOGI, TAG));
2336         }
2337         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
2338 
2339         // Remove A2DP routes as well
2340         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
2341         mmi.record();
2342         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
2343         purgeDevicesRoles_l();
2344         mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
2345     }
2346 
2347     @GuardedBy("mDevicesLock")
2348     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
2349         // prevent any activity on the A2DP audio output to avoid unwanted
2350         // reconnection of the sink.
2351         mDeviceBroker.setA2dpSuspended(
2352                 true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater");
2353         // retrieve DeviceInfo before removing device
2354         final String deviceKey =
2355                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
2356         final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
2357         final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
2358                 AudioSystem.AUDIO_FORMAT_DEFAULT;
2359         // the device will be made unavailable later, so consider it disconnected right away
2360         mConnectedDevices.remove(deviceKey);
2361         // send the delayed message to make the device unavailable later
2362         mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs);
2363     }
2364 
2365 
2366     @GuardedBy("mDevicesLock")
2367     private void makeA2dpSrcAvailable(String address) {
2368         final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
2369                 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
2370                 AudioSystem.DEVICE_STATE_AVAILABLE,
2371                 AudioSystem.AUDIO_FORMAT_DEFAULT);
2372         if (res != AudioSystem.AUDIO_STATUS_OK) {
2373             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2374                     "APM failed to make available A2DP source device addr="
2375                             + Utils.anonymizeBluetoothAddress(address)
2376                             + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
2377             // TODO: connection failed, stop here
2378             // TODO: return
2379         } else {
2380             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2381                     "A2DP source device addr=" + Utils.anonymizeBluetoothAddress(address)
2382                             + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
2383         }
2384         mConnectedDevices.put(
2385                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
2386                 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
2387     }
2388 
2389     @GuardedBy("mDevicesLock")
2390     private void makeA2dpSrcUnavailable(String address) {
2391         AudioDeviceAttributes ada = new AudioDeviceAttributes(
2392                 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
2393         mAudioSystem.setDeviceConnectionState(ada,
2394                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
2395                 AudioSystem.AUDIO_FORMAT_DEFAULT);
2396         mConnectedDevices.remove(
2397                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
2398         mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
2399     }
2400 
2401     @GuardedBy("mDevicesLock")
2402     private void makeHearingAidDeviceAvailable(
2403             String address, String name, int streamType, String eventSource) {
2404         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
2405                 DEVICE_OUT_HEARING_AID);
2406         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
2407 
2408         mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
2409 
2410         AudioDeviceAttributes ada = new AudioDeviceAttributes(
2411                 DEVICE_OUT_HEARING_AID, address, name);
2412         mAudioSystem.setDeviceConnectionState(ada,
2413                 AudioSystem.DEVICE_STATE_AVAILABLE,
2414                 AudioSystem.AUDIO_FORMAT_DEFAULT);
2415         mConnectedDevices.put(
2416                 DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address),
2417                 new DeviceInfo(DEVICE_OUT_HEARING_AID, name, address));
2418         mDeviceBroker.postAccessoryPlugMediaUnmute(DEVICE_OUT_HEARING_AID);
2419         mDeviceBroker.postApplyVolumeOnDevice(streamType,
2420                 DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
2421         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
2422         addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
2423                 BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
2424         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
2425                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
2426                 .set(MediaMetrics.Property.DEVICE,
2427                         AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID))
2428                 .set(MediaMetrics.Property.NAME, name)
2429                 .set(MediaMetrics.Property.STREAM_TYPE,
2430                         AudioSystem.streamToString(streamType))
2431                 .record();
2432     }
2433 
2434     @GuardedBy("mDevicesLock")
2435     private void makeHearingAidDeviceUnavailable(String address) {
2436         AudioDeviceAttributes ada = new AudioDeviceAttributes(
2437                 DEVICE_OUT_HEARING_AID, address);
2438         mAudioSystem.setDeviceConnectionState(ada,
2439                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
2440                 AudioSystem.AUDIO_FORMAT_DEFAULT);
2441         mConnectedDevices.remove(
2442                 DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
2443         // Remove Hearing Aid routes as well
2444         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
2445         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable")
2446                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
2447                 .set(MediaMetrics.Property.DEVICE,
2448                         AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID))
2449                 .record();
2450         mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
2451     }
2452 
2453     /**
2454      * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
2455      * Visibility by APM plays no role
2456      * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
2457      */
2458     boolean isHearingAidConnected() {
2459         return getFirstConnectedDeviceOfTypes(
2460                 Sets.newHashSet(DEVICE_OUT_HEARING_AID)) != null;
2461     }
2462 
2463     /**
2464      * Returns a DeviceInfo for the first connected device matching one of the supplied types
2465      */
2466     private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) {
2467         List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes);
2468         return devices.isEmpty() ? null : devices.get(0);
2469     }
2470 
2471     /**
2472      * Returns a list of connected devices matching one of the supplied types
2473      */
2474     private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
2475         ArrayList<DeviceInfo> devices = new ArrayList<>();
2476         synchronized (mDevicesLock) {
2477             for (DeviceInfo di : mConnectedDevices.values()) {
2478                 if (internalTypes.contains(di.mDeviceType)) {
2479                     devices.add(di);
2480                 }
2481             }
2482         }
2483         return devices;
2484     }
2485 
2486     /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
2487         DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type));
2488         return di == null ? null : new AudioDeviceAttributes(
2489                     di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
2490     }
2491 
2492     @GuardedBy("mDevicesLock")
2493     private void makeLeAudioDeviceAvailable(
2494             AudioDeviceBroker.BtDeviceInfo btInfo, int streamType,
2495             @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, String eventSource) {
2496         final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
2497         final int device = btInfo.mAudioSystemDevice;
2498 
2499         if (device != AudioSystem.DEVICE_NONE) {
2500             final String address = btInfo.mDevice.getAddress();
2501             String name = BtHelper.getName(btInfo.mDevice);
2502 
2503             // Find LE Group ID and peer headset address if available
2504             final int groupId = mDeviceBroker.getLeAudioDeviceGroupId(btInfo.mDevice);
2505             String peerAddress = "";
2506             String peerIdentityAddress = "";
2507             if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
2508                 List<Pair<String, String>> addresses =
2509                         mDeviceBroker.getLeAudioGroupAddresses(groupId);
2510                 if (addresses.size() > 1) {
2511                     for (Pair<String, String> addr : addresses) {
2512                         if (!addr.first.equals(address)) {
2513                             peerAddress = addr.first;
2514                             peerIdentityAddress = addr.second;
2515                             break;
2516                         }
2517                     }
2518                 }
2519             }
2520             // The BT Stack does not provide a name for LE Broadcast devices
2521             if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
2522                 name = "Broadcast";
2523             }
2524 
2525             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
2526              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
2527              */
2528             mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
2529 
2530             AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
2531             final int res = AudioSystem.setDeviceConnectionState(ada,
2532                     AudioSystem.DEVICE_STATE_AVAILABLE, codec);
2533             if (res != AudioSystem.AUDIO_STATUS_OK) {
2534                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2535                         "APM failed to make available LE Audio device addr=" + address
2536                                 + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
2537                 // TODO: connection failed, stop here
2538                 // TODO: return;
2539             } else {
2540                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2541                         "LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
2542                                 + " device addr=" + Utils.anonymizeBluetoothAddress(address)
2543                                 + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
2544             }
2545             // Reset LEA suspend state each time a new sink is connected
2546             mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
2547             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
2548                     new DeviceInfo(device, name, address,
2549                             btInfo.mDevice.getIdentityAddress(), codec,
2550                             groupId, peerAddress, peerIdentityAddress));
2551             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
2552             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
2553             addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
2554                     BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
2555         }
2556 
2557         if (streamType == AudioSystem.STREAM_DEFAULT) {
2558             // No need to update volume for input devices
2559             return;
2560         }
2561 
2562         final int leAudioVolIndex = (volumeIndex == -1)
2563                 ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
2564                 : volumeIndex;
2565         final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
2566         mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
2567         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
2568 
2569         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
2570     }
2571 
2572     @GuardedBy("mDevicesLock")
2573     private void makeLeAudioDeviceUnavailableNow(String address, int device,
2574             @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
2575         AudioDeviceAttributes ada = null;
2576         if (device != AudioSystem.DEVICE_NONE) {
2577             ada = new AudioDeviceAttributes(device, address);
2578             final int res = AudioSystem.setDeviceConnectionState(ada,
2579                     AudioSystem.DEVICE_STATE_UNAVAILABLE,
2580                     codec);
2581 
2582             if (res != AudioSystem.AUDIO_STATUS_OK) {
2583                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2584                         "APM failed to make unavailable LE Audio device addr=" + address
2585                                 + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
2586                 // TODO:  failed to disconnect, stop here
2587                 // TODO: return;
2588             } else {
2589                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
2590                         "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
2591                                 + " made unavailable").printSlog(EventLogger.Event.ALOGI, TAG));
2592             }
2593             mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
2594         }
2595 
2596         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
2597         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
2598         purgeDevicesRoles_l();
2599         if (ada != null) {
2600             mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
2601         }
2602     }
2603 
2604     @GuardedBy("mDevicesLock")
2605     private void makeLeAudioDeviceUnavailableLater(
2606             String address, int device, int codec, int delayMs) {
2607         // prevent any activity on the LEA output to avoid unwanted
2608         // reconnection of the sink.
2609         mDeviceBroker.setLeAudioSuspended(
2610                 true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater");
2611         // the device will be made unavailable later, so consider it disconnected right away
2612         mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
2613         // send the delayed message to make the device unavailable later
2614         mDeviceBroker.setLeAudioTimeout(address, device, codec, delayMs);
2615     }
2616 
2617     @GuardedBy("mDevicesLock")
2618     private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) {
2619         synchronized (mCurAudioRoutes) {
2620             if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
2621                 return;
2622             }
2623             if (name != null || !isCurrentDeviceConnected()) {
2624                 mCurAudioRoutes.bluetoothName = name;
2625                 mDeviceBroker.postReportNewRoutes(fromA2dp);
2626             }
2627         }
2628     }
2629 
2630     @GuardedBy("mDevicesLock")
2631     private boolean isCurrentDeviceConnected() {
2632         return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
2633             TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
2634     }
2635 
2636     // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
2637     // sent if:
2638     // - none of these devices are connected anymore after one is disconnected AND
2639     // - the device being disconnected is actually used for music.
2640     // Access synchronized on mConnectedDevices
2641     private static final Set<Integer> BECOMING_NOISY_INTENT_DEVICES_SET;
2642     static {
2643         BECOMING_NOISY_INTENT_DEVICES_SET = new HashSet<>();
2644         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
2645         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
2646         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI);
2647         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
2648         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
2649         BECOMING_NOISY_INTENT_DEVICES_SET.add(DEVICE_OUT_HEARING_AID);
2650         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET);
2651         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
2652         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(DEVICE_OUT_ALL_A2DP_SET);
2653         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
2654         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(DEVICE_OUT_ALL_BLE_SET);
2655     }
2656 
2657     // must be called before removing the device from mConnectedDevices
2658     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
2659     // from AudioSystem
2660     @GuardedBy("mDevicesLock")
2661     private int checkSendBecomingNoisyIntentInt(int device,
2662             @AudioService.ConnectionState int state, int musicDevice) {
2663         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
2664                 + "checkSendBecomingNoisyIntentInt")
2665                 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device))
2666                 .set(MediaMetrics.Property.STATE,
2667                         state == AudioService.CONNECTION_STATE_CONNECTED
2668                                 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED);
2669         if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
2670             Log.i(TAG, "not sending NOISY: state=" + state);
2671             mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
2672             return 0;
2673         }
2674         if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) {
2675             Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device)
2676                     + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET);
2677             mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
2678             return 0;
2679         }
2680         int delay = 0;
2681         Set<Integer> devices = new HashSet<>();
2682         for (DeviceInfo di : mConnectedDevices.values()) {
2683             if (!AudioSystem.isInputDevice(di.mDeviceType)
2684                     && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) {
2685                 devices.add(di.mDeviceType);
2686                 Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType));
2687             }
2688         }
2689         if (musicDevice == AudioSystem.DEVICE_NONE) {
2690             musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
2691             Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x"
2692                     + Integer.toHexString(musicDevice));
2693         }
2694 
2695         // always ignore condition on device being actually used for music when in communication
2696         // because music routing is altered in this case.
2697         // also checks whether media routing if affected by a dynamic policy or mirroring
2698         final boolean inCommunication = mDeviceBroker.isInCommunication();
2699         final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device);
2700         final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy();
2701         if (((device == musicDevice) || inCommunication)
2702                 && singleAudioDeviceType
2703                 && !hasMediaDynamicPolicy
2704                 && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) {
2705             if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
2706                     && !mDeviceBroker.hasAudioFocusUsers()) {
2707                 // no media playback, not a "becoming noisy" situation, otherwise it could cause
2708                 // the pausing of some apps that are playing remotely
2709                 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
2710                         "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
2711                 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
2712                 return 0;
2713             }
2714             mDeviceBroker.postBroadcastBecomingNoisy();
2715             delay = AudioService.BECOMING_NOISY_DELAY_MS;
2716         } else {
2717             Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device)
2718                     + " musicDevice:0x" + Integer.toHexString(musicDevice)
2719                     + " inComm:" + inCommunication
2720                     + " mediaPolicy:" + hasMediaDynamicPolicy
2721                     + " singleDevice:" + singleAudioDeviceType);
2722         }
2723 
2724         mmi.set(MediaMetrics.Property.DELAY_MS, delay).record();
2725         return delay;
2726     }
2727 
2728     // Intent "extra" data keys.
2729     private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
2730     private static final String CONNECT_INTENT_KEY_STATE = "state";
2731     private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
2732 
2733     private void sendDeviceConnectionIntent(int device, int state, String address,
2734                                             String deviceName) {
2735         if (AudioService.DEBUG_DEVICES) {
2736             Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
2737                     + " state:0x" + Integer.toHexString(state) + " address:" + address
2738                     + " name:" + deviceName + ");");
2739         }
2740         Intent intent = new Intent();
2741 
2742         switch(device) {
2743             case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
2744                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
2745                 intent.putExtra("microphone", 1);
2746                 break;
2747             case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
2748             case AudioSystem.DEVICE_OUT_LINE:
2749                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
2750                 intent.putExtra("microphone", 0);
2751                 break;
2752             case AudioSystem.DEVICE_OUT_USB_HEADSET:
2753                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
2754                 intent.putExtra("microphone",
2755                         AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
2756                                 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
2757                 break;
2758             case AudioSystem.DEVICE_IN_USB_HEADSET:
2759                 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
2760                         == AudioSystem.DEVICE_STATE_AVAILABLE) {
2761                     intent.setAction(Intent.ACTION_HEADSET_PLUG);
2762                     intent.putExtra("microphone", 1);
2763                 } else {
2764                     // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
2765                     return;
2766                 }
2767                 break;
2768             case AudioSystem.DEVICE_OUT_HDMI:
2769             case AudioSystem.DEVICE_OUT_HDMI_ARC:
2770             case AudioSystem.DEVICE_OUT_HDMI_EARC:
2771                 configureHdmiPlugIntent(intent, state);
2772                 break;
2773         }
2774 
2775         if (intent.getAction() == null) {
2776             return;
2777         }
2778 
2779         intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
2780         intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
2781         intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
2782 
2783         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
2784 
2785         final long ident = Binder.clearCallingIdentity();
2786         try {
2787             mDeviceBroker.broadcastStickyIntentToCurrentProfileGroup(intent);
2788         } finally {
2789             Binder.restoreCallingIdentity(ident);
2790         }
2791     }
2792 
2793     private void updateAudioRoutes(int device, int state) {
2794         int connType = 0;
2795 
2796         switch (device) {
2797             case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
2798                 connType = AudioRoutesInfo.MAIN_HEADSET;
2799                 break;
2800             case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
2801             case AudioSystem.DEVICE_OUT_LINE:
2802                 connType = AudioRoutesInfo.MAIN_HEADPHONES;
2803                 break;
2804             case AudioSystem.DEVICE_OUT_HDMI:
2805             case AudioSystem.DEVICE_OUT_HDMI_ARC:
2806             case AudioSystem.DEVICE_OUT_HDMI_EARC:
2807                 connType = AudioRoutesInfo.MAIN_HDMI;
2808                 break;
2809             case AudioSystem.DEVICE_OUT_USB_DEVICE:
2810             case AudioSystem.DEVICE_OUT_USB_HEADSET:
2811                 connType = AudioRoutesInfo.MAIN_USB;
2812                 break;
2813             case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET:
2814                 connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
2815                 break;
2816         }
2817 
2818         synchronized (mCurAudioRoutes) {
2819             if (connType == 0) {
2820                 return;
2821             }
2822             int newConn = mCurAudioRoutes.mainType;
2823             if (state != 0) {
2824                 newConn |= connType;
2825             } else {
2826                 newConn &= ~connType;
2827             }
2828             if (newConn != mCurAudioRoutes.mainType) {
2829                 mCurAudioRoutes.mainType = newConn;
2830                 mDeviceBroker.postReportNewRoutes(false /*fromA2dp*/);
2831             }
2832         }
2833     }
2834 
2835     private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
2836         intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
2837         intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
2838         if (state != AudioService.CONNECTION_STATE_CONNECTED) {
2839             return;
2840         }
2841         ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
2842         int[] portGeneration = new int[1];
2843         int status = AudioSystem.listAudioPorts(ports, portGeneration);
2844         if (status != AudioManager.SUCCESS) {
2845             Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
2846             return;
2847         }
2848         for (AudioPort port : ports) {
2849             if (!(port instanceof AudioDevicePort)) {
2850                 continue;
2851             }
2852             final AudioDevicePort devicePort = (AudioDevicePort) port;
2853             if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
2854                     && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC
2855                     && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_EARC) {
2856                 continue;
2857             }
2858             // found an HDMI port: format the list of supported encodings
2859             int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
2860             if (formats.length > 0) {
2861                 ArrayList<Integer> encodingList = new ArrayList(1);
2862                 for (int format : formats) {
2863                     // a format in the list can be 0, skip it
2864                     if (format != AudioFormat.ENCODING_INVALID) {
2865                         encodingList.add(format);
2866                     }
2867                 }
2868                 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
2869                 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
2870             }
2871             // find the maximum supported number of channels
2872             int maxChannels = 0;
2873             for (int mask : devicePort.channelMasks()) {
2874                 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
2875                 if (channelCount > maxChannels) {
2876                     maxChannels = channelCount;
2877                 }
2878             }
2879             intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
2880         }
2881     }
2882 
2883     private void dispatchPreferredDevice(int strategy,
2884                                          @NonNull List<AudioDeviceAttributes> devices) {
2885         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
2886         for (int i = 0; i < nbDispatchers; i++) {
2887             try {
2888                 if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) {
2889                     devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
2890                 }
2891                 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
2892                         strategy, devices);
2893             } catch (RemoteException e) {
2894                 Log.e(TAG, "dispatchPreferredDevice ", e);
2895             }
2896         }
2897         mPrefDevDispatchers.finishBroadcast();
2898     }
2899 
2900     private void dispatchNonDefaultDevice(int strategy,
2901                                           @NonNull List<AudioDeviceAttributes> devices) {
2902         final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast();
2903         for (int i = 0; i < nbDispatchers; i++) {
2904             try {
2905                 if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) {
2906                     devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
2907                 }
2908                 mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
2909                         strategy, devices);
2910             } catch (RemoteException e) {
2911                 Log.e(TAG, "dispatchNonDefaultDevice ", e);
2912             }
2913         }
2914         mNonDefDevDispatchers.finishBroadcast();
2915     }
2916 
2917     private void dispatchDevicesRoleForCapturePreset(
2918             int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
2919         final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
2920         for (int i = 0; i < nbDispatchers; ++i) {
2921             try {
2922                 if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) {
2923                     devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
2924                 }
2925                 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
2926                         capturePreset, role, devices);
2927             } catch (RemoteException e) {
2928                 Log.e(TAG, "dispatchDevicesRoleForCapturePreset ", e);
2929             }
2930         }
2931         mDevRoleCapturePresetDispatchers.finishBroadcast();
2932     }
2933 
2934     List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
2935         List<String> addresses = new ArrayList<String>();
2936         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
2937                 device.getAddress());
2938         synchronized (mDevicesLock) {
2939             DeviceInfo di = mConnectedDevices.get(key);
2940             if (di != null) {
2941                 if (!di.mDeviceIdentityAddress.isEmpty()) {
2942                     addresses.add(di.mDeviceIdentityAddress);
2943                 }
2944                 if (!di.mPeerIdentityDeviceAddress.isEmpty()
2945                         && !di.mPeerIdentityDeviceAddress.equals(di.mDeviceIdentityAddress)) {
2946                     addresses.add(di.mPeerIdentityDeviceAddress);
2947                 }
2948             }
2949         }
2950         return addresses;
2951     }
2952 
2953     /*package*/ String getDeviceSettings() {
2954         int deviceCatalogSize = 0;
2955         synchronized (mDeviceInventoryLock) {
2956             deviceCatalogSize = mDeviceInventory.size();
2957 
2958             final StringBuilder settingsBuilder = new StringBuilder(
2959                     deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
2960 
2961             Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
2962             if (iterator.hasNext()) {
2963                 settingsBuilder.append(iterator.next().toPersistableString());
2964             }
2965             while (iterator.hasNext()) {
2966                 settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
2967                 settingsBuilder.append(iterator.next().toPersistableString());
2968             }
2969             return settingsBuilder.toString();
2970         }
2971     }
2972 
2973     /*package*/ void setDeviceSettings(String settings) {
2974         clearDeviceInventory();
2975         String[] devSettings = TextUtils.split(Objects.requireNonNull(settings),
2976                 SETTING_DEVICE_SEPARATOR);
2977         // small list, not worth overhead of Arrays.stream(devSettings)
2978         for (String setting : devSettings) {
2979             AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting);
2980             // Note if the device is not compatible with spatialization mode or the device
2981             // type is not canonical, it will be ignored in {@link SpatializerHelper}.
2982             if (devState != null) {
2983                 addOrUpdateDeviceSAStateInInventory(devState, false /*syncInventory*/);
2984                 addOrUpdateAudioDeviceCategoryInInventory(devState, false /*syncInventory*/);
2985             }
2986         }
2987     }
2988 
2989     //----------------------------------------------------------
2990     // For tests only
2991 
2992     /**
2993      * Check if device is in the list of connected devices
2994      * @param device the device to query
2995      * @return true if connected
2996      */
2997     @VisibleForTesting
2998     public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
2999         for (DeviceInfo di : getConnectedDevicesOfTypes(
3000                 Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
3001             if (di.mDeviceAddress.equals(device.getAddress())) {
3002                 return true;
3003             }
3004         }
3005         return false;
3006     }
3007 }
3008