2 * Copyright 2019 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.server.audio;
18 import android.annotation.NonNull;
19 import android.app.ActivityManager;
20 import android.bluetooth.BluetoothA2dp;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHearingAid;
24 import android.bluetooth.BluetoothProfile;
25 import android.content.Intent;
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.os.Binder;
34 import android.os.RemoteCallbackList;
35 import android.os.RemoteException;
36 import android.os.UserHandle;
37 import android.text.TextUtils;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 import android.util.Log;
41 import android.util.Slog;
43 import com.android.internal.annotations.GuardedBy;
45 import java.util.ArrayList;
48 * Class to manage the inventory of all connected devices.
49 * This class is thread-safe.
51 public final class AudioDeviceInventory {
53 private static final String TAG = "AS.AudioDeviceInventory";
55 // Actual list of connected devices
56 // Key for map created from DeviceInfo.makeDeviceListKey()
57 private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
59 private final @NonNull AudioDeviceBroker mDeviceBroker;
61 AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
62 mDeviceBroker = broker;
65 // cache of the address of the last dock the device was connected to
66 private String mDockAddress;
68 // Monitoring of audio routes. Protected by mAudioRoutes.
69 final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
70 final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
71 new RemoteCallbackList<IAudioRoutesObserver>();
73 //------------------------------------------------------------
75 * Class to store info about connected devices.
76 * Use makeDeviceListKey() to make a unique key for this list.
78 private static class DeviceInfo {
79 final int mDeviceType;
80 final String mDeviceName;
81 final String mDeviceAddress;
82 int mDeviceCodecFormat;
84 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
85 mDeviceType = deviceType;
86 mDeviceName = deviceName;
87 mDeviceAddress = deviceAddress;
88 mDeviceCodecFormat = deviceCodecFormat;
92 public String toString() {
93 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
94 + " name:" + mDeviceName
95 + " addr:" + mDeviceAddress
96 + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
100 * Generate a unique key for the mConnectedDevices List by composing the device "type"
101 * and the "address" associated with a specific instance of that device type
103 private static String makeDeviceListKey(int device, String deviceAddress) {
104 return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
109 * A class just for packaging up a set of connection parameters.
111 /*package*/ class WiredDeviceConnectionState {
112 public final int mType;
113 public final @AudioService.ConnectionState int mState;
114 public final String mAddress;
115 public final String mName;
116 public final String mCaller;
118 /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
119 String address, String name, String caller) {
128 //------------------------------------------------------------
129 // Message handling from AudioDeviceBroker
132 * Restore previously connected devices. Use in case of audio server crash
133 * (see AudioService.onAudioServerDied() method)
135 /*package*/ void onRestoreDevices() {
136 synchronized (mConnectedDevices) {
137 for (int i = 0; i < mConnectedDevices.size(); i++) {
138 DeviceInfo di = mConnectedDevices.valueAt(i);
139 AudioSystem.setDeviceConnectionState(
141 AudioSystem.DEVICE_STATE_AVAILABLE,
144 di.mDeviceCodecFormat);
149 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
150 /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
151 @AudioService.BtProfileConnectionState int state) {
152 final BluetoothDevice btDevice = btInfo.getBtDevice();
153 int a2dpVolume = btInfo.getVolume();
154 if (AudioService.DEBUG_DEVICES) {
155 Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
156 + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
158 String address = btDevice.getAddress();
159 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
162 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
163 "A2DP sink connected: device addr=" + address + " state=" + state
164 + " vol=" + a2dpVolume));
166 final int a2dpCodec = btInfo.getCodec();
168 synchronized (mConnectedDevices) {
169 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
170 btDevice.getAddress());
171 final DeviceInfo di = mConnectedDevices.get(key);
172 boolean isConnected = di != null;
174 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
175 if (btDevice.isBluetoothDock()) {
176 if (state == BluetoothProfile.STATE_DISCONNECTED) {
177 // introduction of a delay for transient disconnections of docks when
178 // power is rapidly turned off/on, this message will be canceled if
179 // we reconnect the dock under a preset delay
180 makeA2dpDeviceUnavailableLater(address,
181 AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
182 // the next time isConnected is evaluated, it will be false for the dock
185 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
187 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
188 if (btDevice.isBluetoothDock()) {
189 // this could be a reconnection after a transient disconnection
190 mDeviceBroker.cancelA2dpDockTimeout();
191 mDockAddress = address;
193 // this could be a connection of another A2DP device before the timeout of
194 // a dock: cancel the dock timeout, and make the dock unavailable now
195 if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
196 mDeviceBroker.cancelA2dpDockTimeout();
197 makeA2dpDeviceUnavailableNow(mDockAddress,
198 AudioSystem.AUDIO_FORMAT_DEFAULT);
201 if (a2dpVolume != -1) {
202 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
203 // convert index to internal representation in VolumeStreamState
205 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
207 makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
208 "onSetA2dpSinkConnectionState", a2dpCodec);
213 /*package*/ void onSetA2dpSourceConnectionState(
214 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
215 final BluetoothDevice btDevice = btInfo.getBtDevice();
216 if (AudioService.DEBUG_DEVICES) {
217 Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
220 String address = btDevice.getAddress();
221 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
225 synchronized (mConnectedDevices) {
226 final String key = DeviceInfo.makeDeviceListKey(
227 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
228 final DeviceInfo di = mConnectedDevices.get(key);
229 boolean isConnected = di != null;
231 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
232 makeA2dpSrcUnavailable(address);
233 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
234 makeA2dpSrcAvailable(address);
239 /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
240 @AudioService.BtProfileConnectionState int state, int streamType) {
241 String address = btDevice.getAddress();
242 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
245 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
246 "onSetHearingAidConnectionState addr=" + address));
248 synchronized (mConnectedDevices) {
249 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
250 btDevice.getAddress());
251 final DeviceInfo di = mConnectedDevices.get(key);
252 boolean isConnected = di != null;
254 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
255 makeHearingAidDeviceUnavailable(address);
256 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
257 makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
258 "onSetHearingAidConnectionState");
263 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
264 /*package*/ void onBluetoothA2dpActiveDeviceChange(
265 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
266 final BluetoothDevice btDevice = btInfo.getBtDevice();
267 if (btDevice == null) {
270 if (AudioService.DEBUG_DEVICES) {
271 Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
273 int a2dpVolume = btInfo.getVolume();
274 final int a2dpCodec = btInfo.getCodec();
276 String address = btDevice.getAddress();
277 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
280 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
281 "onBluetoothA2dpActiveDeviceChange addr=" + address
282 + " event=" + BtHelper.a2dpDeviceEventToString(event)));
284 synchronized (mConnectedDevices) {
285 //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
286 // for this type of message
287 if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
288 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
289 "A2dp config change ignored"));
292 final String key = DeviceInfo.makeDeviceListKey(
293 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
294 final DeviceInfo di = mConnectedDevices.get(key);
296 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange");
300 if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
301 // Device is connected
302 if (a2dpVolume != -1) {
303 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
304 // convert index to internal representation in VolumeStreamState
306 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
307 "onBluetoothA2dpActiveDeviceChange");
309 } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
310 if (di.mDeviceCodecFormat != a2dpCodec) {
311 di.mDeviceCodecFormat = a2dpCodec;
312 mConnectedDevices.replace(key, di);
315 if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
316 BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
317 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
318 // force A2DP device disconnection in case of error so that AudioService state is
319 // consistent with audio policy manager state
320 setBluetoothA2dpDeviceConnectionState(
321 btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
322 false /* suppressNoisyIntent */, musicDevice,
323 -1 /* a2dpVolume */);
328 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
329 synchronized (mConnectedDevices) {
330 makeA2dpDeviceUnavailableNow(address, a2dpCodec);
334 /*package*/ void onReportNewRoutes() {
335 int n = mRoutesObservers.beginBroadcast();
337 AudioRoutesInfo routes;
338 synchronized (mCurAudioRoutes) {
339 routes = new AudioRoutesInfo(mCurAudioRoutes);
343 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
345 obs.dispatchAudioRoutesChanged(routes);
346 } catch (RemoteException e) { }
349 mRoutesObservers.finishBroadcast();
350 mDeviceBroker.postObserveDevicesForAllStreams();
353 private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
354 AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
355 | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
357 /*package*/ void onSetWiredDeviceConnectionState(
358 AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
359 AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
361 synchronized (mConnectedDevices) {
362 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
363 && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
364 mDeviceBroker.setBluetoothA2dpOnInt(true,
365 "onSetWiredDeviceConnectionState state DISCONNECTED");
368 if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
369 wdcs.mType, wdcs.mAddress, wdcs.mName)) {
370 // change of connection state failed, bailout
373 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
374 if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
375 mDeviceBroker.setBluetoothA2dpOnInt(false,
376 "onSetWiredDeviceConnectionState state not DISCONNECTED");
378 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
380 if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
381 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
383 sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
384 updateAudioRoutes(wdcs.mType, wdcs.mState);
388 /*package*/ void onToggleHdmi() {
389 synchronized (mConnectedDevices) {
390 // Is HDMI connected?
391 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
392 final DeviceInfo di = mConnectedDevices.get(key);
394 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
397 // Toggle HDMI to retrigger broadcast with proper formats.
398 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
399 AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
400 "android"); // disconnect
401 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
402 AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
403 "android"); // reconnect
406 //------------------------------------------------------------
410 * Implements the communication with AudioSystem to (dis)connect a device in the native layers
411 * @param connect true if connection
412 * @param device the device type
413 * @param address the address of the device
414 * @param deviceName human-readable name of device
415 * @return false if an error was reported by AudioSystem
417 /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
419 if (AudioService.DEBUG_DEVICES) {
420 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
421 + Integer.toHexString(device) + " address:" + address
422 + " name:" + deviceName + ")");
424 synchronized (mConnectedDevices) {
425 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
426 if (AudioService.DEBUG_DEVICES) {
427 Slog.i(TAG, "deviceKey:" + deviceKey);
429 DeviceInfo di = mConnectedDevices.get(deviceKey);
430 boolean isConnected = di != null;
431 if (AudioService.DEBUG_DEVICES) {
432 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
434 if (connect && !isConnected) {
435 final int res = AudioSystem.setDeviceConnectionState(device,
436 AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
437 AudioSystem.AUDIO_FORMAT_DEFAULT);
438 if (res != AudioSystem.AUDIO_STATUS_OK) {
439 Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
440 + " due to command error " + res);
443 mConnectedDevices.put(deviceKey, new DeviceInfo(
444 device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
445 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
447 } else if (!connect && isConnected) {
448 AudioSystem.setDeviceConnectionState(device,
449 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
450 AudioSystem.AUDIO_FORMAT_DEFAULT);
451 // always remove even if disconnection failed
452 mConnectedDevices.remove(deviceKey);
455 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
456 + ", deviceSpec=" + di + ", connect=" + connect);
462 /*package*/ void disconnectA2dp() {
463 synchronized (mConnectedDevices) {
464 final ArraySet<String> toRemove = new ArraySet<>();
465 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
466 mConnectedDevices.values().forEach(deviceInfo -> {
467 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
468 toRemove.add(deviceInfo.mDeviceAddress);
471 if (toRemove.size() > 0) {
472 final int delay = checkSendBecomingNoisyIntentInt(
473 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
474 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
475 toRemove.stream().forEach(deviceAddress ->
476 makeA2dpDeviceUnavailableLater(deviceAddress, delay)
482 /*package*/ void disconnectA2dpSink() {
483 synchronized (mConnectedDevices) {
484 final ArraySet<String> toRemove = new ArraySet<>();
485 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
486 mConnectedDevices.values().forEach(deviceInfo -> {
487 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
488 toRemove.add(deviceInfo.mDeviceAddress);
491 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
495 /*package*/ void disconnectHearingAid() {
496 synchronized (mConnectedDevices) {
497 final ArraySet<String> toRemove = new ArraySet<>();
498 // Disconnect ALL DEVICE_OUT_HEARING_AID devices
499 mConnectedDevices.values().forEach(deviceInfo -> {
500 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
501 toRemove.add(deviceInfo.mDeviceAddress);
504 if (toRemove.size() > 0) {
505 final int delay = checkSendBecomingNoisyIntentInt(
506 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
507 toRemove.stream().forEach(deviceAddress ->
508 // TODO delay not used?
509 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
515 // must be called before removing the device from mConnectedDevices
516 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
518 /*package*/ int checkSendBecomingNoisyIntent(int device,
519 @AudioService.ConnectionState int state, int musicDevice) {
520 synchronized (mConnectedDevices) {
521 return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
525 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
526 synchronized (mCurAudioRoutes) {
527 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
528 mRoutesObservers.register(observer);
533 /*package*/ AudioRoutesInfo getCurAudioRoutes() {
534 return mCurAudioRoutes;
537 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
538 /*package*/ void setBluetoothA2dpDeviceConnectionState(
539 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
540 int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
542 if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
543 throw new IllegalArgumentException("invalid profile " + profile);
545 synchronized (mConnectedDevices) {
546 if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
547 int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
548 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
549 intState, musicDevice);
554 final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
556 if (AudioService.DEBUG_DEVICES) {
557 Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
558 + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
559 + " suppressNoisyIntent: " + suppressNoisyIntent);
562 final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
563 new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
564 if (profile == BluetoothProfile.A2DP) {
566 onSetA2dpSinkConnectionState(a2dpDeviceInfo, state);
568 mDeviceBroker.postA2dpSinkConnection(state,
572 } else { //profile == BluetoothProfile.A2DP_SINK
573 mDeviceBroker.postA2dpSourceConnection(state,
580 /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
581 String address, String name, String caller) {
582 synchronized (mConnectedDevices) {
583 int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
584 mDeviceBroker.postSetWiredDeviceConnectionState(
585 new WiredDeviceConnectionState(type, state, address, name, caller),
591 /*package*/ int setBluetoothHearingAidDeviceConnectionState(
592 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
593 boolean suppressNoisyIntent, int musicDevice) {
595 synchronized (mConnectedDevices) {
596 if (!suppressNoisyIntent) {
597 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
598 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
599 intState, musicDevice);
603 mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
609 //-------------------------------------------------------------------
610 // Internal utilities
612 @GuardedBy("mConnectedDevices")
613 private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
615 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
616 // audio policy manager
617 mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
618 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
619 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
620 // Reset A2DP suspend state each time a new sink is connected
621 AudioSystem.setParameters("A2dpSuspended=false");
622 mConnectedDevices.put(
623 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
624 new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
625 address, a2dpCodec));
626 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
627 setCurrentAudioRouteNameIfPossible(name);
630 @GuardedBy("mConnectedDevices")
631 private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
632 if (address == null) {
635 mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
636 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
637 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
638 mConnectedDevices.remove(
639 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
640 // Remove A2DP routes as well
641 setCurrentAudioRouteNameIfPossible(null);
642 if (mDockAddress == address) {
647 @GuardedBy("mConnectedDevices")
648 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
649 // prevent any activity on the A2DP audio output to avoid unwanted
650 // reconnection of the sink.
651 AudioSystem.setParameters("A2dpSuspended=true");
652 // retrieve DeviceInfo before removing device
653 final String deviceKey =
654 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
655 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
656 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
657 AudioSystem.AUDIO_FORMAT_DEFAULT;
658 // the device will be made unavailable later, so consider it disconnected right away
659 mConnectedDevices.remove(deviceKey);
660 // send the delayed message to make the device unavailable later
661 mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
665 @GuardedBy("mConnectedDevices")
666 private void makeA2dpSrcAvailable(String address) {
667 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
668 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
669 AudioSystem.AUDIO_FORMAT_DEFAULT);
670 mConnectedDevices.put(
671 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
672 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
673 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
676 @GuardedBy("mConnectedDevices")
677 private void makeA2dpSrcUnavailable(String address) {
678 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
679 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
680 AudioSystem.AUDIO_FORMAT_DEFAULT);
681 mConnectedDevices.remove(
682 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
685 @GuardedBy("mConnectedDevices")
686 private void makeHearingAidDeviceAvailable(
687 String address, String name, int streamType, String eventSource) {
688 final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
689 AudioSystem.DEVICE_OUT_HEARING_AID);
690 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
692 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
693 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
694 AudioSystem.AUDIO_FORMAT_DEFAULT);
695 mConnectedDevices.put(
696 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
697 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
698 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
699 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
700 mDeviceBroker.postApplyVolumeOnDevice(streamType,
701 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
702 setCurrentAudioRouteNameIfPossible(name);
705 @GuardedBy("mConnectedDevices")
706 private void makeHearingAidDeviceUnavailable(String address) {
707 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
708 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
709 AudioSystem.AUDIO_FORMAT_DEFAULT);
710 mConnectedDevices.remove(
711 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
712 // Remove Hearing Aid routes as well
713 setCurrentAudioRouteNameIfPossible(null);
716 @GuardedBy("mConnectedDevices")
717 private void setCurrentAudioRouteNameIfPossible(String name) {
718 synchronized (mCurAudioRoutes) {
719 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
722 if (name != null || !isCurrentDeviceConnected()) {
723 mCurAudioRoutes.bluetoothName = name;
724 mDeviceBroker.postReportNewRoutes();
729 @GuardedBy("mConnectedDevices")
730 private boolean isCurrentDeviceConnected() {
731 return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
732 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
735 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
737 // - none of these devices are connected anymore after one is disconnected AND
738 // - the device being disconnected is actually used for music.
739 // Access synchronized on mConnectedDevices
740 private int mBecomingNoisyIntentDevices =
741 AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
742 | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
743 | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
744 | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
745 | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
746 | AudioSystem.DEVICE_OUT_HEARING_AID;
748 // must be called before removing the device from mConnectedDevices
749 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
751 @GuardedBy("mConnectedDevices")
752 private int checkSendBecomingNoisyIntentInt(int device,
753 @AudioService.ConnectionState int state, int musicDevice) {
754 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
757 if ((device & mBecomingNoisyIntentDevices) == 0) {
762 for (int i = 0; i < mConnectedDevices.size(); i++) {
763 int dev = mConnectedDevices.valueAt(i).mDeviceType;
764 if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
765 && ((dev & mBecomingNoisyIntentDevices) != 0)) {
769 if (musicDevice == AudioSystem.DEVICE_NONE) {
770 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
773 // always ignore condition on device being actually used for music when in communication
774 // because music routing is altered in this case.
775 // also checks whether media routing if affected by a dynamic policy or mirroring
776 if (((device == musicDevice) || mDeviceBroker.isInCommunication())
777 && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()
778 && ((musicDevice & AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) == 0)) {
779 if (!AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
780 && !mDeviceBroker.hasAudioFocusUsers()) {
781 // no media playback, not a "becoming noisy" situation, otherwise it could cause
782 // the pausing of some apps that are playing remotely
783 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
784 "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
787 mDeviceBroker.postBroadcastBecomingNoisy();
794 // Intent "extra" data keys.
795 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
796 private static final String CONNECT_INTENT_KEY_STATE = "state";
797 private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
798 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
799 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
800 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
801 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
803 private void sendDeviceConnectionIntent(int device, int state, String address,
805 if (AudioService.DEBUG_DEVICES) {
806 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
807 + " state:0x" + Integer.toHexString(state) + " address:" + address
808 + " name:" + deviceName + ");");
810 Intent intent = new Intent();
813 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
814 intent.setAction(Intent.ACTION_HEADSET_PLUG);
815 intent.putExtra("microphone", 1);
817 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
818 case AudioSystem.DEVICE_OUT_LINE:
819 intent.setAction(Intent.ACTION_HEADSET_PLUG);
820 intent.putExtra("microphone", 0);
822 case AudioSystem.DEVICE_OUT_USB_HEADSET:
823 intent.setAction(Intent.ACTION_HEADSET_PLUG);
824 intent.putExtra("microphone",
825 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
826 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
828 case AudioSystem.DEVICE_IN_USB_HEADSET:
829 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
830 == AudioSystem.DEVICE_STATE_AVAILABLE) {
831 intent.setAction(Intent.ACTION_HEADSET_PLUG);
832 intent.putExtra("microphone", 1);
834 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
838 case AudioSystem.DEVICE_OUT_HDMI:
839 case AudioSystem.DEVICE_OUT_HDMI_ARC:
840 configureHdmiPlugIntent(intent, state);
844 if (intent.getAction() == null) {
848 intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
849 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
850 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
852 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
854 final long ident = Binder.clearCallingIdentity();
856 ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT);
858 Binder.restoreCallingIdentity(ident);
862 private void updateAudioRoutes(int device, int state) {
866 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
867 connType = AudioRoutesInfo.MAIN_HEADSET;
869 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
870 case AudioSystem.DEVICE_OUT_LINE:
871 connType = AudioRoutesInfo.MAIN_HEADPHONES;
873 case AudioSystem.DEVICE_OUT_HDMI:
874 case AudioSystem.DEVICE_OUT_HDMI_ARC:
875 connType = AudioRoutesInfo.MAIN_HDMI;
877 case AudioSystem.DEVICE_OUT_USB_DEVICE:
878 case AudioSystem.DEVICE_OUT_USB_HEADSET:
879 connType = AudioRoutesInfo.MAIN_USB;
883 synchronized (mCurAudioRoutes) {
887 int newConn = mCurAudioRoutes.mainType;
891 newConn &= ~connType;
893 if (newConn != mCurAudioRoutes.mainType) {
894 mCurAudioRoutes.mainType = newConn;
895 mDeviceBroker.postReportNewRoutes();
900 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
901 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
902 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
903 if (state != AudioService.CONNECTION_STATE_CONNECTED) {
906 ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
907 int[] portGeneration = new int[1];
908 int status = AudioSystem.listAudioPorts(ports, portGeneration);
909 if (status != AudioManager.SUCCESS) {
910 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
913 for (AudioPort port : ports) {
914 if (!(port instanceof AudioDevicePort)) {
917 final AudioDevicePort devicePort = (AudioDevicePort) port;
918 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
919 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
922 // found an HDMI port: format the list of supported encodings
923 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
924 if (formats.length > 0) {
925 ArrayList<Integer> encodingList = new ArrayList(1);
926 for (int format : formats) {
927 // a format in the list can be 0, skip it
928 if (format != AudioFormat.ENCODING_INVALID) {
929 encodingList.add(format);
932 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
933 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
935 // find the maximum supported number of channels
937 for (int mask : devicePort.channelMasks()) {
938 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
939 if (channelCount > maxChannels) {
940 maxChannels = channelCount;
943 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);