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