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