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