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