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