• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.audio;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothProfile;
23 import android.content.Intent;
24 import android.media.AudioDeviceAttributes;
25 import android.media.AudioDevicePort;
26 import android.media.AudioFormat;
27 import android.media.AudioManager;
28 import android.media.AudioPort;
29 import android.media.AudioRoutesInfo;
30 import android.media.AudioSystem;
31 import android.media.IAudioRoutesObserver;
32 import android.media.ICapturePresetDevicesRoleDispatcher;
33 import android.media.IStrategyPreferredDevicesDispatcher;
34 import android.media.MediaMetrics;
35 import android.os.Binder;
36 import android.os.RemoteCallbackList;
37 import android.os.RemoteException;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Log;
42 import android.util.Slog;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.LinkedHashMap;
51 import java.util.List;
52 import java.util.Objects;
53 import java.util.Set;
54 import java.util.UUID;
55 
56 /**
57  * Class to manage the inventory of all connected devices.
58  * This class is thread-safe.
59  * (non final for mocking/spying)
60  */
61 public class AudioDeviceInventory {
62 
63     private static final String TAG = "AS.AudioDeviceInventory";
64 
65     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
66     private final Object mDevicesLock = new Object();
67 
68     //Audio Analytics ids.
69     private static final String mMetricsId = "audio.device.";
70 
71     // List of connected devices
72     // Key for map created from DeviceInfo.makeDeviceListKey()
73     @GuardedBy("mDevicesLock")
74     private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>() {
75         @Override
76         public DeviceInfo put(String key, DeviceInfo value) {
77             final DeviceInfo result = super.put(key, value);
78             record("put", true /* connected */, key, value);
79             return result;
80         }
81 
82         @Override
83         public DeviceInfo putIfAbsent(String key, DeviceInfo value) {
84             final DeviceInfo result = super.putIfAbsent(key, value);
85             if (result == null) {
86                 record("putIfAbsent", true /* connected */, key, value);
87             }
88             return result;
89         }
90 
91         @Override
92         public DeviceInfo remove(Object key) {
93             final DeviceInfo result = super.remove(key);
94             if (result != null) {
95                 record("remove", false /* connected */, (String) key, result);
96             }
97             return result;
98         }
99 
100         @Override
101         public boolean remove(Object key, Object value) {
102             final boolean result = super.remove(key, value);
103             if (result) {
104                 record("remove", false /* connected */, (String) key, (DeviceInfo) value);
105             }
106             return result;
107         }
108 
109         // Not overridden
110         // clear
111         // compute
112         // computeIfAbsent
113         // computeIfPresent
114         // merge
115         // putAll
116         // replace
117         // replaceAll
118         private void record(String event, boolean connected, String key, DeviceInfo value) {
119             // DeviceInfo - int mDeviceType;
120             // DeviceInfo - int mDeviceCodecFormat;
121             new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
122                     + MediaMetrics.SEPARATOR + AudioSystem.getDeviceName(value.mDeviceType))
123                     .set(MediaMetrics.Property.ADDRESS, value.mDeviceAddress)
124                     .set(MediaMetrics.Property.EVENT, event)
125                     .set(MediaMetrics.Property.NAME, value.mDeviceName)
126                     .set(MediaMetrics.Property.STATE, connected
127                             ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
128                     .record();
129         }
130     };
131 
132     // List of devices actually connected to AudioPolicy (through AudioSystem), only one
133     // by device type, which is used as the key, value is the DeviceInfo generated key.
134     // For the moment only for A2DP sink devices.
135     // TODO: extend to all device types
136     @GuardedBy("mDevicesLock")
137     private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
138 
139     // List of preferred devices for strategies
140     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices =
141             new ArrayMap<>();
142 
143     // List of preferred devices of capture preset
144     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset =
145             new ArrayMap<>();
146 
147     // the wrapper for AudioSystem static methods, allows us to spy AudioSystem
148     private final @NonNull AudioSystemAdapter mAudioSystem;
149 
150     private @NonNull AudioDeviceBroker mDeviceBroker;
151 
152     // Monitoring of audio routes.  Protected by mAudioRoutes.
153     final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
154     final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
155             new RemoteCallbackList<IAudioRoutesObserver>();
156 
157     // Monitoring of strategy-preferred device
158     final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers =
159             new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>();
160 
161     // Monitoring of devices for role and capture preset
162     final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
163             new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
164 
AudioDeviceInventory(@onNull AudioDeviceBroker broker)165     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
166         mDeviceBroker = broker;
167         mAudioSystem = AudioSystemAdapter.getDefaultAdapter();
168     }
169 
170     //-----------------------------------------------------------
171     /** for mocking only, allows to inject AudioSystem adapter */
AudioDeviceInventory(@onNull AudioSystemAdapter audioSystem)172     /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) {
173         mDeviceBroker = null;
174         mAudioSystem = audioSystem;
175     }
176 
setDeviceBroker(@onNull AudioDeviceBroker broker)177     /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
178         mDeviceBroker = broker;
179     }
180 
181     //------------------------------------------------------------
182     /**
183      * Class to store info about connected devices.
184      * Use makeDeviceListKey() to make a unique key for this list.
185      */
186     private static class DeviceInfo {
187         final int mDeviceType;
188         final @NonNull String mDeviceName;
189         final @NonNull String mDeviceAddress;
190         int mDeviceCodecFormat;
191         final UUID mSensorUuid;
192 
DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat, UUID sensorUuid)193         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
194                    int deviceCodecFormat, UUID sensorUuid) {
195             mDeviceType = deviceType;
196             mDeviceName = deviceName == null ? "" : deviceName;
197             mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
198             mDeviceCodecFormat = deviceCodecFormat;
199             mSensorUuid = sensorUuid;
200         }
201 
DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat)202         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
203                    int deviceCodecFormat) {
204             this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
205         }
206 
207         @Override
toString()208         public String toString() {
209             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
210                     + " (" + AudioSystem.getDeviceName(mDeviceType)
211                     + ") name:" + mDeviceName
212                     + " addr:" + mDeviceAddress
213                     + " codec: " + Integer.toHexString(mDeviceCodecFormat)
214                     + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
215         }
216 
getKey()217         @NonNull String getKey() {
218             return makeDeviceListKey(mDeviceType, mDeviceAddress);
219         }
220 
221         /**
222          * Generate a unique key for the mConnectedDevices List by composing the device "type"
223          * and the "address" associated with a specific instance of that device type
224          */
makeDeviceListKey(int device, String deviceAddress)225         @NonNull private static String makeDeviceListKey(int device, String deviceAddress) {
226             return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
227         }
228     }
229 
230     /**
231      * A class just for packaging up a set of connection parameters.
232      */
233     /*package*/ class WiredDeviceConnectionState {
234         public final AudioDeviceAttributes mAttributes;
235         public final @AudioService.ConnectionState int mState;
236         public final String mCaller;
237         public boolean mForTest = false;
238 
WiredDeviceConnectionState(AudioDeviceAttributes attributes, @AudioService.ConnectionState int state, String caller)239         /*package*/ WiredDeviceConnectionState(AudioDeviceAttributes attributes,
240                 @AudioService.ConnectionState int state, String caller) {
241             mAttributes = attributes;
242             mState = state;
243             mCaller = caller;
244         }
245     }
246 
247     //------------------------------------------------------------
dump(PrintWriter pw, String prefix)248     /*package*/ void dump(PrintWriter pw, String prefix) {
249         pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET=");
250         BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> {
251             pw.print(" 0x" +  Integer.toHexString(device)); });
252         pw.println("\n" + prefix + "Preferred devices for strategy:");
253         mPreferredDevices.forEach((strategy, device) -> {
254             pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
255         pw.println("\n" + prefix + "Connected devices:");
256         mConnectedDevices.forEach((key, deviceInfo) -> {
257             pw.println("  " + prefix + deviceInfo.toString()); });
258         pw.println("\n" + prefix + "APM Connected device (A2DP sink only):");
259         mApmConnectedDevices.forEach((keyType, valueAddress) -> {
260             pw.println("  " + prefix + " type:0x" + Integer.toHexString(keyType)
261                     + " (" + AudioSystem.getDeviceName(keyType)
262                     + ") addr:" + valueAddress); });
263         mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
264             pw.println("  " + prefix + "capturePreset:" + capturePreset
265                     + " devices:" + devices); });
266     }
267 
268     //------------------------------------------------------------
269     // Message handling from AudioDeviceBroker
270 
271     /**
272      * Restore previously connected devices. Use in case of audio server crash
273      * (see AudioService.onAudioServerDied() method)
274      */
275     // Always executed on AudioDeviceBroker message queue
onRestoreDevices()276     /*package*/ void onRestoreDevices() {
277         synchronized (mDevicesLock) {
278             //TODO iterate on mApmConnectedDevices instead once it handles all device types
279             for (DeviceInfo di : mConnectedDevices.values()) {
280                 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType,
281                         di.mDeviceAddress,
282                         di.mDeviceName),
283                         AudioSystem.DEVICE_STATE_AVAILABLE,
284                         di.mDeviceCodecFormat);
285             }
286         }
287         synchronized (mPreferredDevices) {
288             mPreferredDevices.forEach((strategy, devices) -> {
289                 mAudioSystem.setDevicesRoleForStrategy(
290                         strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
291         }
292         synchronized (mPreferredDevicesForCapturePreset) {
293             // TODO: call audiosystem to restore
294         }
295     }
296 
297     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
298     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
onSetBtActiveDevice(@onNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType)299     void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
300         if (AudioService.DEBUG_DEVICES) {
301             Log.d(TAG, "onSetBtActiveDevice"
302                     + " btDevice=" + btInfo.mDevice
303                     + " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile)
304                     + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState));
305         }
306         String address = btInfo.mDevice.getAddress();
307         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
308             address = "";
309         }
310 
311         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent("BT connected:"
312                         + " addr=" + address
313                         + " profile=" + btInfo.mProfile
314                         + " state=" + btInfo.mState
315                         + " codec=" + AudioSystem.audioFormatToString(btInfo.mCodec)));
316 
317         new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice")
318                 .set(MediaMetrics.Property.STATUS, btInfo.mProfile)
319                 .set(MediaMetrics.Property.DEVICE,
320                         AudioSystem.getDeviceName(btInfo.mAudioSystemDevice))
321                 .set(MediaMetrics.Property.ADDRESS, address)
322                 .set(MediaMetrics.Property.ENCODING,
323                         AudioSystem.audioFormatToString(btInfo.mCodec))
324                 .set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice")
325                 .set(MediaMetrics.Property.STREAM_TYPE,
326                         AudioSystem.streamToString(streamType))
327                 .set(MediaMetrics.Property.STATE,
328                         btInfo.mState == BluetoothProfile.STATE_CONNECTED
329                         ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
330                 .record();
331 
332         synchronized (mDevicesLock) {
333             final String key = DeviceInfo.makeDeviceListKey(btInfo.mAudioSystemDevice, address);
334             final DeviceInfo di = mConnectedDevices.get(key);
335 
336             final boolean isConnected = di != null;
337 
338             final boolean switchToUnavailable = isConnected
339                     && btInfo.mState != BluetoothProfile.STATE_CONNECTED;
340             final boolean switchToAvailable = !isConnected
341                     && btInfo.mState == BluetoothProfile.STATE_CONNECTED;
342 
343             switch (btInfo.mProfile) {
344                 case BluetoothProfile.A2DP_SINK:
345                     if (switchToUnavailable) {
346                         makeA2dpSrcUnavailable(address);
347                     } else if (switchToAvailable) {
348                         makeA2dpSrcAvailable(address);
349                     }
350                     break;
351                 case BluetoothProfile.A2DP:
352                     if (switchToUnavailable) {
353                         makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
354                     } else if (switchToAvailable) {
355                         // device is not already connected
356                         if (btInfo.mVolume != -1) {
357                             mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
358                                     // convert index to internal representation in VolumeStreamState
359                                     btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
360                                     "onSetBtActiveDevice");
361                         }
362                         makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
363                                 "onSetBtActiveDevice", btInfo.mCodec);
364                     }
365                     break;
366                 case BluetoothProfile.HEARING_AID:
367                     if (switchToUnavailable) {
368                         makeHearingAidDeviceUnavailable(address);
369                     } else if (switchToAvailable) {
370                         makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
371                                 streamType, "onSetBtActiveDevice");
372                     }
373                     break;
374                 case BluetoothProfile.LE_AUDIO:
375                 case BluetoothProfile.LE_AUDIO_BROADCAST:
376                     if (switchToUnavailable) {
377                         makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
378                     } else if (switchToAvailable) {
379                         makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
380                                 streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
381                                 btInfo.mAudioSystemDevice,
382                                 "onSetBtActiveDevice");
383                     }
384                     break;
385                 default: throw new IllegalArgumentException("Invalid profile "
386                                  + BluetoothProfile.getProfileName(btInfo.mProfile));
387             }
388         }
389     }
390 
391 
392     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
onBluetoothA2dpDeviceConfigChange( @onNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event)393         /*package*/ void onBluetoothA2dpDeviceConfigChange(
394             @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
395         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
396                 + "onBluetoothA2dpDeviceConfigChange")
397                 .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
398 
399         final BluetoothDevice btDevice = btInfo.getBtDevice();
400         if (btDevice == null) {
401             mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
402             return;
403         }
404         if (AudioService.DEBUG_DEVICES) {
405             Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
406         }
407         int a2dpVolume = btInfo.getVolume();
408         @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec();
409 
410         String address = btDevice.getAddress();
411         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
412             address = "";
413         }
414         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
415                 "onBluetoothA2dpDeviceConfigChange addr=" + address
416                     + " event=" + BtHelper.a2dpDeviceEventToString(event)));
417 
418         synchronized (mDevicesLock) {
419             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
420                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
421                         "A2dp config change ignored (scheduled connection change)")
422                         .printLog(TAG));
423                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
424                         .record();
425                 return;
426             }
427             final String key = DeviceInfo.makeDeviceListKey(
428                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
429             final DeviceInfo di = mConnectedDevices.get(key);
430             if (di == null) {
431                 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
432                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
433                 return;
434             }
435 
436             mmi.set(MediaMetrics.Property.ADDRESS, address)
437                     .set(MediaMetrics.Property.ENCODING,
438                             AudioSystem.audioFormatToString(a2dpCodec))
439                     .set(MediaMetrics.Property.INDEX, a2dpVolume)
440                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
441 
442             if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
443                 // Device is connected
444                 if (a2dpVolume != -1) {
445                     mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
446                             // convert index to internal representation in VolumeStreamState
447                             a2dpVolume * 10,
448                             AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
449                             "onBluetoothA2dpDeviceConfigChange");
450                 }
451             } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
452                 if (di.mDeviceCodecFormat != a2dpCodec) {
453                     di.mDeviceCodecFormat = a2dpCodec;
454                     mConnectedDevices.replace(key, di);
455                 }
456             }
457             final int res = mAudioSystem.handleDeviceConfigChange(
458                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
459                     BtHelper.getName(btDevice), a2dpCodec);
460 
461             if (res != AudioSystem.AUDIO_STATUS_OK) {
462                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
463                         "APM handleDeviceConfigChange failed for A2DP device addr=" + address
464                                 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
465                         .printLog(TAG));
466 
467                 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
468                 // force A2DP device disconnection in case of error so that AudioService state is
469                 // consistent with audio policy manager state
470                 setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice,
471                                 BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
472                                 musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
473             } else {
474                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
475                         "APM handleDeviceConfigChange success for A2DP device addr=" + address
476                                 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
477                         .printLog(TAG));
478             }
479         }
480         mmi.record();
481     }
482 
onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec)483     /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
484         synchronized (mDevicesLock) {
485             makeA2dpDeviceUnavailableNow(address, a2dpCodec);
486         }
487     }
488 
onMakeLeAudioDeviceUnavailableNow(String address, int device)489     /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) {
490         synchronized (mDevicesLock) {
491             makeLeAudioDeviceUnavailableNow(address, device);
492         }
493     }
494 
onReportNewRoutes()495     /*package*/ void onReportNewRoutes() {
496         int n = mRoutesObservers.beginBroadcast();
497         if (n > 0) {
498             new MediaMetrics.Item(mMetricsId + "onReportNewRoutes")
499                     .set(MediaMetrics.Property.OBSERVERS, n)
500                     .record();
501             AudioRoutesInfo routes;
502             synchronized (mCurAudioRoutes) {
503                 routes = new AudioRoutesInfo(mCurAudioRoutes);
504             }
505             while (n > 0) {
506                 n--;
507                 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
508                 try {
509                     obs.dispatchAudioRoutesChanged(routes);
510                 } catch (RemoteException e) { }
511             }
512         }
513         mRoutesObservers.finishBroadcast();
514         mDeviceBroker.postObserveDevicesForAllStreams();
515     }
516 
517     /* package */ static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET;
518     static {
519         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>();
520         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
521         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
522         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE);
523         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
524     }
525 
onSetWiredDeviceConnectionState( AudioDeviceInventory.WiredDeviceConnectionState wdcs)526     /*package*/ void onSetWiredDeviceConnectionState(
527                             AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
528         int type = wdcs.mAttributes.getInternalType();
529 
530         AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
531 
532         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
533                 + "onSetWiredDeviceConnectionState")
534                 .set(MediaMetrics.Property.ADDRESS, wdcs.mAttributes.getAddress())
535                 .set(MediaMetrics.Property.DEVICE,
536                         AudioSystem.getDeviceName(type))
537                 .set(MediaMetrics.Property.STATE,
538                         wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
539                                 ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED);
540         synchronized (mDevicesLock) {
541             if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
542                     && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
543                 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/,
544                         "onSetWiredDeviceConnectionState state DISCONNECTED");
545             }
546 
547             if (!handleDeviceConnection(wdcs.mAttributes,
548                     wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) {
549                 // change of connection state failed, bailout
550                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
551                         .record();
552                 return;
553             }
554             if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
555                 if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
556                     mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/,
557                             "onSetWiredDeviceConnectionState state not DISCONNECTED");
558                 }
559                 mDeviceBroker.checkMusicActive(type, wdcs.mCaller);
560             }
561             if (type == AudioSystem.DEVICE_OUT_HDMI) {
562                 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
563             }
564             sendDeviceConnectionIntent(type, wdcs.mState,
565                     wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
566             updateAudioRoutes(type, wdcs.mState);
567         }
568         mmi.record();
569     }
570 
onToggleHdmi()571     /*package*/ void onToggleHdmi() {
572         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
573                 .set(MediaMetrics.Property.DEVICE,
574                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI));
575         synchronized (mDevicesLock) {
576             // Is HDMI connected?
577             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
578             final DeviceInfo di = mConnectedDevices.get(key);
579             if (di == null) {
580                 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
581                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record();
582                 return;
583             }
584             // Toggle HDMI to retrigger broadcast with proper formats.
585             setWiredDeviceConnectionState(
586                     new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
587                     AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect
588             setWiredDeviceConnectionState(
589                     new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
590                     AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect
591         }
592         mmi.record();
593     }
594 
onSaveSetPreferredDevices(int strategy, @NonNull List<AudioDeviceAttributes> devices)595     /*package*/ void onSaveSetPreferredDevices(int strategy,
596                                                @NonNull List<AudioDeviceAttributes> devices) {
597         mPreferredDevices.put(strategy, devices);
598         dispatchPreferredDevice(strategy, devices);
599     }
600 
onSaveRemovePreferredDevices(int strategy)601     /*package*/ void onSaveRemovePreferredDevices(int strategy) {
602         mPreferredDevices.remove(strategy);
603         dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>());
604     }
605 
onSaveSetPreferredDevicesForCapturePreset( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)606     /*package*/ void onSaveSetPreferredDevicesForCapturePreset(
607             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
608         mPreferredDevicesForCapturePreset.put(capturePreset, devices);
609         dispatchDevicesRoleForCapturePreset(
610                 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
611     }
612 
onSaveClearPreferredDevicesForCapturePreset(int capturePreset)613     /*package*/ void onSaveClearPreferredDevicesForCapturePreset(int capturePreset) {
614         mPreferredDevicesForCapturePreset.remove(capturePreset);
615         dispatchDevicesRoleForCapturePreset(
616                 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED,
617                 new ArrayList<AudioDeviceAttributes>());
618     }
619 
620     //------------------------------------------------------------
621     // preferred device(s)
622 
setPreferredDevicesForStrategySync(int strategy, @NonNull List<AudioDeviceAttributes> devices)623     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
624             @NonNull List<AudioDeviceAttributes> devices) {
625         final long identity = Binder.clearCallingIdentity();
626 
627         AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
628                                 "setPreferredDevicesForStrategySync, strategy: " + strategy
629                                 + " devices: " + devices)).printLog(TAG));
630         final int status = mAudioSystem.setDevicesRoleForStrategy(
631                 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
632         Binder.restoreCallingIdentity(identity);
633 
634         if (status == AudioSystem.SUCCESS) {
635             mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
636         }
637         return status;
638     }
639 
removePreferredDevicesForStrategySync(int strategy)640     /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
641         final long identity = Binder.clearCallingIdentity();
642 
643         AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
644                 "removePreferredDevicesForStrategySync, strategy: "
645                 + strategy)).printLog(TAG));
646 
647         final int status = mAudioSystem.removeDevicesRoleForStrategy(
648                 strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
649         Binder.restoreCallingIdentity(identity);
650 
651         if (status == AudioSystem.SUCCESS) {
652             mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
653         }
654         return status;
655     }
656 
registerStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)657     /*package*/ void registerStrategyPreferredDevicesDispatcher(
658             @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
659         mPrefDevDispatchers.register(dispatcher);
660     }
661 
unregisterStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)662     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
663             @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
664         mPrefDevDispatchers.unregister(dispatcher);
665     }
666 
setPreferredDevicesForCapturePresetSync( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)667     /*package*/ int setPreferredDevicesForCapturePresetSync(
668             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
669         final long identity = Binder.clearCallingIdentity();
670         final int status = mAudioSystem.setDevicesRoleForCapturePreset(
671                 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
672         Binder.restoreCallingIdentity(identity);
673 
674         if (status == AudioSystem.SUCCESS) {
675             mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
676         }
677         return status;
678     }
679 
clearPreferredDevicesForCapturePresetSync(int capturePreset)680     /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
681         final long identity = Binder.clearCallingIdentity();
682         final int status = mAudioSystem.clearDevicesRoleForCapturePreset(
683                 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
684         Binder.restoreCallingIdentity(identity);
685 
686         if (status == AudioSystem.SUCCESS) {
687             mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
688         }
689         return status;
690     }
691 
registerCapturePresetDevicesRoleDispatcher( @onNull ICapturePresetDevicesRoleDispatcher dispatcher)692     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
693             @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
694         mDevRoleCapturePresetDispatchers.register(dispatcher);
695     }
696 
unregisterCapturePresetDevicesRoleDispatcher( @onNull ICapturePresetDevicesRoleDispatcher dispatcher)697     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
698             @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
699         mDevRoleCapturePresetDispatchers.unregister(dispatcher);
700     }
701 
702     //-----------------------------------------------------------------------
703 
704     /**
705      * Check if a device is in the list of connected devices
706      * @param device the device whose connection state is queried
707      * @return true if connected
708      */
709     // called with AudioDeviceBroker.mDeviceStateLock lock held
isDeviceConnected(@onNull AudioDeviceAttributes device)710     public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
711         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
712                 device.getAddress());
713         synchronized (mDevicesLock) {
714             return (mConnectedDevices.get(key) != null);
715         }
716     }
717 
718     /**
719      * Implements the communication with AudioSystem to (dis)connect a device in the native layers
720      * @param attributes the attributes of the device
721      * @param connect true if connection
722      * @param isForTesting if true, not calling AudioSystem for the connection as this is
723      *                    just for testing
724      * @return false if an error was reported by AudioSystem
725      */
handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, boolean isForTesting)726     /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
727             boolean isForTesting) {
728         int device = attributes.getInternalType();
729         String address = attributes.getAddress();
730         String deviceName = attributes.getName();
731         if (AudioService.DEBUG_DEVICES) {
732             Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
733                     + Integer.toHexString(device) + " address:" + address
734                     + " name:" + deviceName + ")");
735         }
736         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection")
737                 .set(MediaMetrics.Property.ADDRESS, address)
738                 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device))
739                 .set(MediaMetrics.Property.MODE, connect
740                         ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
741                 .set(MediaMetrics.Property.NAME, deviceName);
742         synchronized (mDevicesLock) {
743             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
744             if (AudioService.DEBUG_DEVICES) {
745                 Slog.i(TAG, "deviceKey:" + deviceKey);
746             }
747             DeviceInfo di = mConnectedDevices.get(deviceKey);
748             boolean isConnected = di != null;
749             if (AudioService.DEBUG_DEVICES) {
750                 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
751             }
752             if (connect && !isConnected) {
753                 final int res;
754                 if (isForTesting) {
755                     res = AudioSystem.AUDIO_STATUS_OK;
756                 } else {
757                     res = mAudioSystem.setDeviceConnectionState(attributes,
758                             AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
759                 }
760                 if (res != AudioSystem.AUDIO_STATUS_OK) {
761                     final String reason = "not connecting device 0x" + Integer.toHexString(device)
762                             + " due to command error " + res;
763                     Slog.e(TAG, reason);
764                     mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
765                             .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED)
766                             .record();
767                     return false;
768                 }
769                 mConnectedDevices.put(deviceKey, new DeviceInfo(
770                         device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
771                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
772                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
773                 return true;
774             } else if (!connect && isConnected) {
775                 mAudioSystem.setDeviceConnectionState(attributes,
776                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
777                 // always remove even if disconnection failed
778                 mConnectedDevices.remove(deviceKey);
779                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
780                 return true;
781             }
782             Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
783                     + ", deviceSpec=" + di + ", connect=" + connect);
784         }
785         mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
786         return false;
787     }
788 
789 
disconnectA2dp()790     private void disconnectA2dp() {
791         synchronized (mDevicesLock) {
792             final ArraySet<String> toRemove = new ArraySet<>();
793             // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
794             mConnectedDevices.values().forEach(deviceInfo -> {
795                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
796                     toRemove.add(deviceInfo.mDeviceAddress);
797                 }
798             });
799             new MediaMetrics.Item(mMetricsId + "disconnectA2dp")
800                     .record();
801             if (toRemove.size() > 0) {
802                 final int delay = checkSendBecomingNoisyIntentInt(
803                         AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
804                         AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
805                 toRemove.stream().forEach(deviceAddress ->
806                         makeA2dpDeviceUnavailableLater(deviceAddress, delay)
807                 );
808             }
809         }
810     }
811 
disconnectA2dpSink()812     private void disconnectA2dpSink() {
813         synchronized (mDevicesLock) {
814             final ArraySet<String> toRemove = new ArraySet<>();
815             // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
816             mConnectedDevices.values().forEach(deviceInfo -> {
817                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
818                     toRemove.add(deviceInfo.mDeviceAddress);
819                 }
820             });
821             new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink")
822                     .record();
823             toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
824         }
825     }
826 
disconnectHearingAid()827     private void disconnectHearingAid() {
828         synchronized (mDevicesLock) {
829             final ArraySet<String> toRemove = new ArraySet<>();
830             // Disconnect ALL DEVICE_OUT_HEARING_AID devices
831             mConnectedDevices.values().forEach(deviceInfo -> {
832                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
833                     toRemove.add(deviceInfo.mDeviceAddress);
834                 }
835             });
836             new MediaMetrics.Item(mMetricsId + "disconnectHearingAid")
837                     .record();
838             if (toRemove.size() > 0) {
839                 final int delay = checkSendBecomingNoisyIntentInt(
840                         AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
841                 toRemove.stream().forEach(deviceAddress ->
842                         // TODO delay not used?
843                         makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
844                 );
845             }
846         }
847     }
848 
onBtProfileDisconnected(int profile)849     /*package*/ synchronized void onBtProfileDisconnected(int profile) {
850         switch (profile) {
851             case BluetoothProfile.A2DP:
852                 disconnectA2dp();
853                 break;
854             case BluetoothProfile.A2DP_SINK:
855                 disconnectA2dpSink();
856                 break;
857             case BluetoothProfile.HEARING_AID:
858                 disconnectHearingAid();
859                 break;
860             case BluetoothProfile.LE_AUDIO:
861                 disconnectLeAudioUnicast();
862                 break;
863             case BluetoothProfile.LE_AUDIO_BROADCAST:
864                 disconnectLeAudioBroadcast();
865                 break;
866             default:
867                 // Not a valid profile to disconnect
868                 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
869                         + BluetoothProfile.getProfileName(profile));
870                 break;
871         }
872     }
873 
disconnectLeAudio(int device)874      /*package*/ void disconnectLeAudio(int device) {
875         if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET
876                 && device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) {
877             Log.e(TAG, "disconnectLeAudio: Can't disconnect not LE Audio device " + device);
878             return;
879         }
880 
881         synchronized (mDevicesLock) {
882             final ArraySet<String> toRemove = new ArraySet<>();
883             // Disconnect ALL DEVICE_OUT_BLE_HEADSET or DEVICE_OUT_BLE_BROADCAST devices
884             mConnectedDevices.values().forEach(deviceInfo -> {
885                 if (deviceInfo.mDeviceType == device) {
886                     toRemove.add(deviceInfo.mDeviceAddress);
887                 }
888             });
889             new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
890                     .record();
891             if (toRemove.size() > 0) {
892                 final int delay = checkSendBecomingNoisyIntentInt(device,
893                         AudioService.CONNECTION_STATE_DISCONNECTED,
894                         AudioSystem.DEVICE_NONE);
895                 toRemove.stream().forEach(deviceAddress ->
896                         makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay)
897                 );
898             }
899         }
900     }
901 
disconnectLeAudioUnicast()902     /*package*/ void disconnectLeAudioUnicast() {
903         disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_HEADSET);
904     }
905 
disconnectLeAudioBroadcast()906     /*package*/ void disconnectLeAudioBroadcast() {
907         disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
908     }
909 
910     // must be called before removing the device from mConnectedDevices
911     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
912     // from AudioSystem
checkSendBecomingNoisyIntent(int device, @AudioService.ConnectionState int state, int musicDevice)913     /*package*/ int checkSendBecomingNoisyIntent(int device,
914             @AudioService.ConnectionState int state, int musicDevice) {
915         synchronized (mDevicesLock) {
916             return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
917         }
918     }
919 
startWatchingRoutes(IAudioRoutesObserver observer)920     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
921         synchronized (mCurAudioRoutes) {
922             AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
923             mRoutesObservers.register(observer);
924             return routes;
925         }
926     }
927 
getCurAudioRoutes()928     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
929         return mCurAudioRoutes;
930     }
931 
932     // only public for mocking/spying
933     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
934     @VisibleForTesting
setBluetoothActiveDevice(@onNull AudioDeviceBroker.BtDeviceInfo info)935     public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) {
936         int delay;
937         synchronized (mDevicesLock) {
938             if (!info.mSupprNoisy
939                     && (((info.mProfile == BluetoothProfile.LE_AUDIO
940                         || info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST)
941                         && info.mIsLeOutput)
942                         || info.mProfile == BluetoothProfile.HEARING_AID
943                         || info.mProfile == BluetoothProfile.A2DP)) {
944                 @AudioService.ConnectionState int asState =
945                         (info.mState == BluetoothProfile.STATE_CONNECTED)
946                                 ? AudioService.CONNECTION_STATE_CONNECTED
947                                 : AudioService.CONNECTION_STATE_DISCONNECTED;
948                 delay = checkSendBecomingNoisyIntentInt(info.mAudioSystemDevice, asState,
949                         info.mMusicDevice);
950             } else {
951                 delay = 0;
952             }
953 
954             if (AudioService.DEBUG_DEVICES) {
955                 Log.i(TAG, "setBluetoothActiveDevice device: " + info.mDevice
956                         + " profile: " + BluetoothProfile.getProfileName(info.mProfile)
957                         + " state: " + BluetoothProfile.getConnectionStateName(info.mState)
958                         + " delay(ms): " + delay
959                         + " codec:" + Integer.toHexString(info.mCodec)
960                         + " suppressNoisyIntent: " + info.mSupprNoisy);
961             }
962             mDeviceBroker.postBluetoothActiveDevice(info, delay);
963             if (info.mProfile == BluetoothProfile.HEARING_AID
964                     && info.mState == BluetoothProfile.STATE_CONNECTED) {
965                 mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE,
966                                 "HEARING_AID set to CONNECTED");
967             }
968         }
969         return delay;
970     }
971 
setWiredDeviceConnectionState(AudioDeviceAttributes attributes, @AudioService.ConnectionState int state, String caller)972     /*package*/ int setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
973             @AudioService.ConnectionState int state, String caller) {
974         synchronized (mDevicesLock) {
975             int delay = checkSendBecomingNoisyIntentInt(
976                     attributes.getInternalType(), state, AudioSystem.DEVICE_NONE);
977             mDeviceBroker.postSetWiredDeviceConnectionState(
978                     new WiredDeviceConnectionState(attributes, state, caller), delay);
979             return delay;
980         }
981     }
982 
setTestDeviceConnectionState(@onNull AudioDeviceAttributes device, @AudioService.ConnectionState int state)983     /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
984             @AudioService.ConnectionState int state) {
985         final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
986                 device, state, "com.android.server.audio");
987         connection.mForTest = true;
988         onSetWiredDeviceConnectionState(connection);
989     }
990 
991     //-------------------------------------------------------------------
992     // Internal utilities
993 
994     @GuardedBy("mDevicesLock")
makeA2dpDeviceAvailable(String address, String name, String eventSource, int a2dpCodec)995     private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
996             int a2dpCodec) {
997         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
998         // audio policy manager
999         mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
1000         // at this point there could be another A2DP device already connected in APM, but it
1001         // doesn't matter as this new one will overwrite the previous one
1002         final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1003                 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
1004                 AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
1005 
1006         // TODO: log in MediaMetrics once distinction between connection failure and
1007         // double connection is made.
1008         if (res != AudioSystem.AUDIO_STATUS_OK) {
1009             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
1010                     "APM failed to make available A2DP device addr=" + address
1011                             + " error=" + res).printLog(TAG));
1012             // TODO: connection failed, stop here
1013             // TODO: return;
1014         } else {
1015             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
1016                     "A2DP device addr=" + address + " now available").printLog(TAG));
1017         }
1018 
1019         // Reset A2DP suspend state each time a new sink is connected
1020         mAudioSystem.setParameters("A2dpSuspended=false");
1021 
1022         // The convention for head tracking sensors associated with A2DP devices is to
1023         // use a UUID derived from the MAC address as follows:
1024         //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
1025         UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
1026                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
1027         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
1028                 address, a2dpCodec, sensorUuid);
1029         final String diKey = di.getKey();
1030         mConnectedDevices.put(diKey, di);
1031         // on a connection always overwrite the device seen by AudioPolicy, see comment above when
1032         // calling AudioSystem
1033         mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey);
1034 
1035         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
1036         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
1037     }
1038 
1039     @GuardedBy("mDevicesLock")
makeA2dpDeviceUnavailableNow(String address, int a2dpCodec)1040     private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
1041         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
1042                 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec))
1043                 .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
1044 
1045         if (address == null) {
1046             mmi.set(MediaMetrics.Property.EARLY_RETURN, "address null").record();
1047             return;
1048         }
1049         final String deviceToRemoveKey =
1050                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
1051 
1052         mConnectedDevices.remove(deviceToRemoveKey);
1053         if (!deviceToRemoveKey
1054                 .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
1055             // removing A2DP device not currently used by AudioPolicy, log but don't act on it
1056             AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
1057                     "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
1058             mmi.set(MediaMetrics.Property.EARLY_RETURN,
1059                     "A2DP device made unavailable, was not used")
1060                     .record();
1061             return;
1062         }
1063 
1064         // device to remove was visible by APM, update APM
1065         mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
1066         final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1067                 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
1068                 AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
1069 
1070         if (res != AudioSystem.AUDIO_STATUS_OK) {
1071             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
1072                     "APM failed to make unavailable A2DP device addr=" + address
1073                             + " error=" + res).printLog(TAG));
1074             // TODO:  failed to disconnect, stop here
1075             // TODO: return;
1076         } else {
1077             AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
1078                     "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
1079         }
1080         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
1081         // Remove A2DP routes as well
1082         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
1083         mmi.record();
1084     }
1085 
1086     @GuardedBy("mDevicesLock")
makeA2dpDeviceUnavailableLater(String address, int delayMs)1087     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
1088         // prevent any activity on the A2DP audio output to avoid unwanted
1089         // reconnection of the sink.
1090         mAudioSystem.setParameters("A2dpSuspended=true");
1091         // retrieve DeviceInfo before removing device
1092         final String deviceKey =
1093                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
1094         final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
1095         final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
1096                 AudioSystem.AUDIO_FORMAT_DEFAULT;
1097         // the device will be made unavailable later, so consider it disconnected right away
1098         mConnectedDevices.remove(deviceKey);
1099         // send the delayed message to make the device unavailable later
1100         mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs);
1101     }
1102 
1103 
1104     @GuardedBy("mDevicesLock")
makeA2dpSrcAvailable(String address)1105     private void makeA2dpSrcAvailable(String address) {
1106         mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1107                 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
1108                 AudioSystem.DEVICE_STATE_AVAILABLE,
1109                 AudioSystem.AUDIO_FORMAT_DEFAULT);
1110         mConnectedDevices.put(
1111                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
1112                 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
1113                         address, AudioSystem.AUDIO_FORMAT_DEFAULT));
1114     }
1115 
1116     @GuardedBy("mDevicesLock")
makeA2dpSrcUnavailable(String address)1117     private void makeA2dpSrcUnavailable(String address) {
1118         mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1119                 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
1120                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
1121                 AudioSystem.AUDIO_FORMAT_DEFAULT);
1122         mConnectedDevices.remove(
1123                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
1124     }
1125 
1126     @GuardedBy("mDevicesLock")
makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource)1127     private void makeHearingAidDeviceAvailable(
1128             String address, String name, int streamType, String eventSource) {
1129         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
1130                 AudioSystem.DEVICE_OUT_HEARING_AID);
1131         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
1132 
1133         mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1134                 AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
1135                 AudioSystem.DEVICE_STATE_AVAILABLE,
1136                 AudioSystem.AUDIO_FORMAT_DEFAULT);
1137         mConnectedDevices.put(
1138                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
1139                 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
1140                         address, AudioSystem.AUDIO_FORMAT_DEFAULT));
1141         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
1142         mDeviceBroker.postApplyVolumeOnDevice(streamType,
1143                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
1144         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
1145         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
1146                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
1147                 .set(MediaMetrics.Property.DEVICE,
1148                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
1149                 .set(MediaMetrics.Property.NAME, name)
1150                 .set(MediaMetrics.Property.STREAM_TYPE,
1151                         AudioSystem.streamToString(streamType))
1152                 .record();
1153     }
1154 
1155     @GuardedBy("mDevicesLock")
makeHearingAidDeviceUnavailable(String address)1156     private void makeHearingAidDeviceUnavailable(String address) {
1157         mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1158                 AudioSystem.DEVICE_OUT_HEARING_AID, address),
1159                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
1160                 AudioSystem.AUDIO_FORMAT_DEFAULT);
1161         mConnectedDevices.remove(
1162                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
1163         // Remove Hearing Aid routes as well
1164         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
1165         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable")
1166                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
1167                 .set(MediaMetrics.Property.DEVICE,
1168                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
1169                 .record();
1170     }
1171 
1172     /**
1173      * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
1174      * Visibility by APM plays no role
1175      * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
1176      */
isHearingAidConnected()1177     boolean isHearingAidConnected() {
1178         synchronized (mDevicesLock) {
1179             for (DeviceInfo di : mConnectedDevices.values()) {
1180                 if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
1181                     return true;
1182                 }
1183             }
1184             return false;
1185         }
1186     }
1187 
1188     @GuardedBy("mDevicesLock")
makeLeAudioDeviceAvailable(String address, String name, int streamType, int volumeIndex, int device, String eventSource)1189     private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
1190             int volumeIndex, int device, String eventSource) {
1191         if (device != AudioSystem.DEVICE_NONE) {
1192             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
1193              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
1194              */
1195             mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
1196 
1197             final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1198                     device, address, name),
1199                     AudioSystem.DEVICE_STATE_AVAILABLE,
1200                     AudioSystem.AUDIO_FORMAT_DEFAULT);
1201             if (res != AudioSystem.AUDIO_STATUS_OK) {
1202                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
1203                         "APM failed to make available LE Audio device addr=" + address
1204                                 + " error=" + res).printLog(TAG));
1205                 // TODO: connection failed, stop here
1206                 // TODO: return;
1207             } else {
1208                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
1209                         "LE Audio device addr=" + address + " now available").printLog(TAG));
1210             }
1211 
1212             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
1213                     new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
1214             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
1215             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
1216         }
1217 
1218         if (streamType == AudioSystem.STREAM_DEFAULT) {
1219             // No need to update volume for input devices
1220             return;
1221         }
1222 
1223         final int leAudioVolIndex = (volumeIndex == -1)
1224                 ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
1225                 : volumeIndex;
1226         final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
1227         mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
1228         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
1229     }
1230 
1231     @GuardedBy("mDevicesLock")
makeLeAudioDeviceUnavailableNow(String address, int device)1232     private void makeLeAudioDeviceUnavailableNow(String address, int device) {
1233         if (device != AudioSystem.DEVICE_NONE) {
1234             final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
1235                     device, address),
1236                     AudioSystem.DEVICE_STATE_UNAVAILABLE,
1237                     AudioSystem.AUDIO_FORMAT_DEFAULT);
1238 
1239             if (res != AudioSystem.AUDIO_STATUS_OK) {
1240                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
1241                         "APM failed to make unavailable LE Audio device addr=" + address
1242                                 + " error=" + res).printLog(TAG));
1243                 // TODO:  failed to disconnect, stop here
1244                 // TODO: return;
1245             } else {
1246                 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
1247                         "LE Audio device addr=" + address + " made unavailable")).printLog(TAG));
1248             }
1249             mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
1250         }
1251 
1252         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
1253     }
1254 
1255     @GuardedBy("mDevicesLock")
makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs)1256     private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
1257         // the device will be made unavailable later, so consider it disconnected right away
1258         mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
1259         // send the delayed message to make the device unavailable later
1260         mDeviceBroker.setLeAudioTimeout(address, device, delayMs);
1261     }
1262 
1263     @GuardedBy("mDevicesLock")
setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp)1264     private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) {
1265         synchronized (mCurAudioRoutes) {
1266             if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
1267                 return;
1268             }
1269             if (name != null || !isCurrentDeviceConnected()) {
1270                 mCurAudioRoutes.bluetoothName = name;
1271                 mDeviceBroker.postReportNewRoutes(fromA2dp);
1272             }
1273         }
1274     }
1275 
1276     @GuardedBy("mDevicesLock")
isCurrentDeviceConnected()1277     private boolean isCurrentDeviceConnected() {
1278         return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
1279             TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
1280     }
1281 
1282     // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
1283     // sent if:
1284     // - none of these devices are connected anymore after one is disconnected AND
1285     // - the device being disconnected is actually used for music.
1286     // Access synchronized on mConnectedDevices
1287     private static final Set<Integer> BECOMING_NOISY_INTENT_DEVICES_SET;
1288     static {
1289         BECOMING_NOISY_INTENT_DEVICES_SET = new HashSet<>();
1290         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
1291         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
1292         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI);
1293         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
1294         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
1295         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
1296         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET);
1297         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
1298         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
1299         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
1300         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
1301     }
1302 
1303     // must be called before removing the device from mConnectedDevices
1304     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
1305     // from AudioSystem
1306     @GuardedBy("mDevicesLock")
checkSendBecomingNoisyIntentInt(int device, @AudioService.ConnectionState int state, int musicDevice)1307     private int checkSendBecomingNoisyIntentInt(int device,
1308             @AudioService.ConnectionState int state, int musicDevice) {
1309         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
1310                 + "checkSendBecomingNoisyIntentInt")
1311                 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device))
1312                 .set(MediaMetrics.Property.STATE,
1313                         state == AudioService.CONNECTION_STATE_CONNECTED
1314                                 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED);
1315         if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
1316             Log.i(TAG, "not sending NOISY: state=" + state);
1317             mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
1318             return 0;
1319         }
1320         if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) {
1321             Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device)
1322                     + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET);
1323             mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
1324             return 0;
1325         }
1326         int delay = 0;
1327         Set<Integer> devices = new HashSet<>();
1328         for (DeviceInfo di : mConnectedDevices.values()) {
1329             if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0)
1330                     && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) {
1331                 devices.add(di.mDeviceType);
1332                 Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType));
1333             }
1334         }
1335         if (musicDevice == AudioSystem.DEVICE_NONE) {
1336             musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
1337             Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x"
1338                     + Integer.toHexString(musicDevice));
1339         }
1340 
1341         // always ignore condition on device being actually used for music when in communication
1342         // because music routing is altered in this case.
1343         // also checks whether media routing if affected by a dynamic policy or mirroring
1344         final boolean inCommunication = mDeviceBroker.isInCommunication();
1345         final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device);
1346         final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy();
1347         if (((device == musicDevice) || inCommunication)
1348                 && singleAudioDeviceType
1349                 && !hasMediaDynamicPolicy
1350                 && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) {
1351             if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
1352                     && !mDeviceBroker.hasAudioFocusUsers()) {
1353                 // no media playback, not a "becoming noisy" situation, otherwise it could cause
1354                 // the pausing of some apps that are playing remotely
1355                 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
1356                         "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
1357                 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
1358                 return 0;
1359             }
1360             mDeviceBroker.postBroadcastBecomingNoisy();
1361             delay = AudioService.BECOMING_NOISY_DELAY_MS;
1362         } else {
1363             Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device)
1364                     + " musicDevice:0x" + Integer.toHexString(musicDevice)
1365                     + " inComm:" + inCommunication
1366                     + " mediaPolicy:" + hasMediaDynamicPolicy
1367                     + " singleDevice:" + singleAudioDeviceType);
1368         }
1369 
1370         mmi.set(MediaMetrics.Property.DELAY_MS, delay).record();
1371         return delay;
1372     }
1373 
1374     // Intent "extra" data keys.
1375     private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
1376     private static final String CONNECT_INTENT_KEY_STATE = "state";
1377     private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
1378     private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
1379     private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
1380     private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
1381     private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
1382 
sendDeviceConnectionIntent(int device, int state, String address, String deviceName)1383     private void sendDeviceConnectionIntent(int device, int state, String address,
1384                                             String deviceName) {
1385         if (AudioService.DEBUG_DEVICES) {
1386             Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
1387                     + " state:0x" + Integer.toHexString(state) + " address:" + address
1388                     + " name:" + deviceName + ");");
1389         }
1390         Intent intent = new Intent();
1391 
1392         switch(device) {
1393             case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
1394                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
1395                 intent.putExtra("microphone", 1);
1396                 break;
1397             case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
1398             case AudioSystem.DEVICE_OUT_LINE:
1399                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
1400                 intent.putExtra("microphone", 0);
1401                 break;
1402             case AudioSystem.DEVICE_OUT_USB_HEADSET:
1403                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
1404                 intent.putExtra("microphone",
1405                         AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
1406                                 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
1407                 break;
1408             case AudioSystem.DEVICE_IN_USB_HEADSET:
1409                 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
1410                         == AudioSystem.DEVICE_STATE_AVAILABLE) {
1411                     intent.setAction(Intent.ACTION_HEADSET_PLUG);
1412                     intent.putExtra("microphone", 1);
1413                 } else {
1414                     // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
1415                     return;
1416                 }
1417                 break;
1418             case AudioSystem.DEVICE_OUT_HDMI:
1419             case AudioSystem.DEVICE_OUT_HDMI_ARC:
1420             case AudioSystem.DEVICE_OUT_HDMI_EARC:
1421                 configureHdmiPlugIntent(intent, state);
1422                 break;
1423         }
1424 
1425         if (intent.getAction() == null) {
1426             return;
1427         }
1428 
1429         intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
1430         intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
1431         intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
1432 
1433         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
1434 
1435         final long ident = Binder.clearCallingIdentity();
1436         try {
1437             mDeviceBroker.broadcastStickyIntentToCurrentProfileGroup(intent);
1438         } finally {
1439             Binder.restoreCallingIdentity(ident);
1440         }
1441     }
1442 
updateAudioRoutes(int device, int state)1443     private void updateAudioRoutes(int device, int state) {
1444         int connType = 0;
1445 
1446         switch (device) {
1447             case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
1448                 connType = AudioRoutesInfo.MAIN_HEADSET;
1449                 break;
1450             case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
1451             case AudioSystem.DEVICE_OUT_LINE:
1452                 connType = AudioRoutesInfo.MAIN_HEADPHONES;
1453                 break;
1454             case AudioSystem.DEVICE_OUT_HDMI:
1455             case AudioSystem.DEVICE_OUT_HDMI_ARC:
1456             case AudioSystem.DEVICE_OUT_HDMI_EARC:
1457                 connType = AudioRoutesInfo.MAIN_HDMI;
1458                 break;
1459             case AudioSystem.DEVICE_OUT_USB_DEVICE:
1460             case AudioSystem.DEVICE_OUT_USB_HEADSET:
1461                 connType = AudioRoutesInfo.MAIN_USB;
1462                 break;
1463             case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET:
1464                 connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
1465                 break;
1466         }
1467 
1468         synchronized (mCurAudioRoutes) {
1469             if (connType == 0) {
1470                 return;
1471             }
1472             int newConn = mCurAudioRoutes.mainType;
1473             if (state != 0) {
1474                 newConn |= connType;
1475             } else {
1476                 newConn &= ~connType;
1477             }
1478             if (newConn != mCurAudioRoutes.mainType) {
1479                 mCurAudioRoutes.mainType = newConn;
1480                 mDeviceBroker.postReportNewRoutes(false /*fromA2dp*/);
1481             }
1482         }
1483     }
1484 
configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state)1485     private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
1486         intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
1487         intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
1488         if (state != AudioService.CONNECTION_STATE_CONNECTED) {
1489             return;
1490         }
1491         ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
1492         int[] portGeneration = new int[1];
1493         int status = AudioSystem.listAudioPorts(ports, portGeneration);
1494         if (status != AudioManager.SUCCESS) {
1495             Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
1496             return;
1497         }
1498         for (AudioPort port : ports) {
1499             if (!(port instanceof AudioDevicePort)) {
1500                 continue;
1501             }
1502             final AudioDevicePort devicePort = (AudioDevicePort) port;
1503             if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
1504                     && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC
1505                     && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_EARC) {
1506                 continue;
1507             }
1508             // found an HDMI port: format the list of supported encodings
1509             int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
1510             if (formats.length > 0) {
1511                 ArrayList<Integer> encodingList = new ArrayList(1);
1512                 for (int format : formats) {
1513                     // a format in the list can be 0, skip it
1514                     if (format != AudioFormat.ENCODING_INVALID) {
1515                         encodingList.add(format);
1516                     }
1517                 }
1518                 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
1519                 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
1520             }
1521             // find the maximum supported number of channels
1522             int maxChannels = 0;
1523             for (int mask : devicePort.channelMasks()) {
1524                 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
1525                 if (channelCount > maxChannels) {
1526                     maxChannels = channelCount;
1527                 }
1528             }
1529             intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
1530         }
1531     }
1532 
dispatchPreferredDevice(int strategy, @NonNull List<AudioDeviceAttributes> devices)1533     private void dispatchPreferredDevice(int strategy,
1534                                          @NonNull List<AudioDeviceAttributes> devices) {
1535         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
1536         for (int i = 0; i < nbDispatchers; i++) {
1537             try {
1538                 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
1539                         strategy, devices);
1540             } catch (RemoteException e) {
1541             }
1542         }
1543         mPrefDevDispatchers.finishBroadcast();
1544     }
1545 
dispatchDevicesRoleForCapturePreset( int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)1546     private void dispatchDevicesRoleForCapturePreset(
1547             int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
1548         final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
1549         for (int i = 0; i < nbDispatchers; ++i) {
1550             try {
1551                 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
1552                         capturePreset, role, devices);
1553             } catch (RemoteException e) {
1554             }
1555         }
1556         mDevRoleCapturePresetDispatchers.finishBroadcast();
1557     }
1558 
getDeviceSensorUuid(AudioDeviceAttributes device)1559     @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
1560         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
1561                 device.getAddress());
1562         synchronized (mDevicesLock) {
1563             DeviceInfo di = mConnectedDevices.get(key);
1564             if (di == null) {
1565                 return null;
1566             }
1567             return di.mSensorUuid;
1568         }
1569     }
1570 
getDeviceOfType(int type)1571     /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
1572         synchronized (mDevicesLock) {
1573             for (DeviceInfo di : mConnectedDevices.values()) {
1574                 if (di.mDeviceType == type) {
1575                     return new AudioDeviceAttributes(
1576                             di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
1577                 }
1578             }
1579         }
1580         return null;
1581     }
1582 
1583     //----------------------------------------------------------
1584     // For tests only
1585 
1586     /**
1587      * Check if device is in the list of connected devices
1588      * @param device
1589      * @return true if connected
1590      */
1591     @VisibleForTesting
isA2dpDeviceConnected(@onNull BluetoothDevice device)1592     public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
1593         final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
1594                 device.getAddress());
1595         synchronized (mDevicesLock) {
1596             return (mConnectedDevices.get(key) != null);
1597         }
1598     }
1599 }
1600