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