blob: 41a3c9859f23e399a1344805627afd8309d85323 [file] [log] [blame]
Jean-Michel Trivi58850372018-09-14 16:01:28 -07001/*
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 */
16package com.android.server.audio;
17
18import android.annotation.NonNull;
19import android.app.ActivityManager;
20import android.bluetooth.BluetoothA2dp;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothHearingAid;
24import android.bluetooth.BluetoothProfile;
25import android.content.Intent;
26import android.media.AudioDevicePort;
27import android.media.AudioFormat;
28import android.media.AudioManager;
29import android.media.AudioPort;
30import android.media.AudioRoutesInfo;
31import android.media.AudioSystem;
32import android.media.IAudioRoutesObserver;
33import android.os.Binder;
34import android.os.RemoteCallbackList;
35import android.os.RemoteException;
36import android.os.UserHandle;
37import android.text.TextUtils;
38import android.util.ArrayMap;
39import android.util.ArraySet;
40import android.util.Log;
41import android.util.Slog;
42
43import com.android.internal.annotations.GuardedBy;
44
45import java.util.ArrayList;
46
47/**
48 * Class to manage the inventory of all connected devices.
49 * This class is thread-safe.
50 */
51public final class AudioDeviceInventory {
52
53 private static final String TAG = "AS.AudioDeviceInventory";
54
55 // Actual list of connected devices
56 // Key for map created from DeviceInfo.makeDeviceListKey()
57 private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
58
59 private final @NonNull AudioDeviceBroker mDeviceBroker;
60
61 AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
62 mDeviceBroker = broker;
63 }
64
65 // cache of the address of the last dock the device was connected to
66 private String mDockAddress;
67
68 // Monitoring of audio routes. Protected by mAudioRoutes.
69 final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
70 final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
71 new RemoteCallbackList<IAudioRoutesObserver>();
72
73 //------------------------------------------------------------
74 /**
75 * Class to store info about connected devices.
76 * Use makeDeviceListKey() to make a unique key for this list.
77 */
78 private static class DeviceInfo {
79 final int mDeviceType;
80 final String mDeviceName;
81 final String mDeviceAddress;
82 int mDeviceCodecFormat;
83
84 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
85 mDeviceType = deviceType;
86 mDeviceName = deviceName;
87 mDeviceAddress = deviceAddress;
88 mDeviceCodecFormat = deviceCodecFormat;
89 }
90
91 @Override
92 public String toString() {
93 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
94 + " name:" + mDeviceName
95 + " addr:" + mDeviceAddress
96 + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
97 }
98
99 /**
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
102 */
103 private static String makeDeviceListKey(int device, String deviceAddress) {
104 return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
105 }
106 }
107
108 /**
109 * A class just for packaging up a set of connection parameters.
110 */
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;
117
118 /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
119 String address, String name, String caller) {
120 mType = type;
121 mState = state;
122 mAddress = address;
123 mName = name;
124 mCaller = caller;
125 }
126 }
127
128 //------------------------------------------------------------
129 // Message handling from AudioDeviceBroker
130
131 /**
132 * Restore previously connected devices. Use in case of audio server crash
133 * (see AudioService.onAudioServerDied() method)
134 */
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(
140 di.mDeviceType,
141 AudioSystem.DEVICE_STATE_AVAILABLE,
142 di.mDeviceAddress,
143 di.mDeviceName,
144 di.mDeviceCodecFormat);
145 }
146 }
147 }
148
149 /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800150 @AudioService.BtProfileConnectionState int state) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700151 final BluetoothDevice btDevice = btInfo.getBtDevice();
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800152 int a2dpVolume = btInfo.getVolume();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700153 if (AudioService.DEBUG_DEVICES) {
154 Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800155 + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700156 }
157 String address = btDevice.getAddress();
158 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
159 address = "";
160 }
161 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800162 "A2DP sink connected: device addr=" + address + " state=" + state
163 + " vol=" + a2dpVolume));
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700164
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800165 final int a2dpCodec = btInfo.getCodec();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700166
167 synchronized (mConnectedDevices) {
168 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
169 btDevice.getAddress());
170 final DeviceInfo di = mConnectedDevices.get(key);
171 boolean isConnected = di != null;
172
173 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
174 if (btDevice.isBluetoothDock()) {
175 if (state == BluetoothProfile.STATE_DISCONNECTED) {
176 // introduction of a delay for transient disconnections of docks when
177 // power is rapidly turned off/on, this message will be canceled if
178 // we reconnect the dock under a preset delay
179 makeA2dpDeviceUnavailableLater(address,
180 AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
181 // the next time isConnected is evaluated, it will be false for the dock
182 }
183 } else {
184 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
185 }
186 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
187 if (btDevice.isBluetoothDock()) {
188 // this could be a reconnection after a transient disconnection
189 mDeviceBroker.cancelA2dpDockTimeout();
190 mDockAddress = address;
191 } else {
192 // this could be a connection of another A2DP device before the timeout of
193 // a dock: cancel the dock timeout, and make the dock unavailable now
194 if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
195 mDeviceBroker.cancelA2dpDockTimeout();
196 makeA2dpDeviceUnavailableNow(mDockAddress,
197 AudioSystem.AUDIO_FORMAT_DEFAULT);
198 }
199 }
200 if (a2dpVolume != -1) {
201 AudioService.VolumeStreamState streamState =
202 mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
203 // Convert index to internal representation in VolumeStreamState
204 a2dpVolume = a2dpVolume * 10;
205 streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
206 "onSetA2dpSinkConnectionState");
207 mDeviceBroker.setDeviceVolume(
208 streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
209 }
210 makeA2dpDeviceAvailable(address, btDevice.getName(),
211 "onSetA2dpSinkConnectionState", a2dpCodec);
212 }
213 }
214 }
215
216 /*package*/ void onSetA2dpSourceConnectionState(
217 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
218 final BluetoothDevice btDevice = btInfo.getBtDevice();
219 if (AudioService.DEBUG_DEVICES) {
220 Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
221 + state);
222 }
223 String address = btDevice.getAddress();
224 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
225 address = "";
226 }
227
228 synchronized (mConnectedDevices) {
229 final String key = DeviceInfo.makeDeviceListKey(
230 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
231 final DeviceInfo di = mConnectedDevices.get(key);
232 boolean isConnected = di != null;
233
234 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
235 makeA2dpSrcUnavailable(address);
236 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
237 makeA2dpSrcAvailable(address);
238 }
239 }
240 }
241
242 /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
243 @AudioService.BtProfileConnectionState int state) {
244 String address = btDevice.getAddress();
245 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
246 address = "";
247 }
248 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
249 "onSetHearingAidConnectionState addr=" + address));
250
251 synchronized (mConnectedDevices) {
252 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
253 btDevice.getAddress());
254 final DeviceInfo di = mConnectedDevices.get(key);
255 boolean isConnected = di != null;
256
257 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
258 makeHearingAidDeviceUnavailable(address);
259 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
260 makeHearingAidDeviceAvailable(address, btDevice.getName(),
261 "onSetHearingAidConnectionState");
262 }
263 }
264 }
265
266 /*package*/ void onBluetoothA2dpDeviceConfigChange(
267 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
268 final BluetoothDevice btDevice = btInfo.getBtDevice();
269 if (AudioService.DEBUG_DEVICES) {
270 Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
271 }
272 if (btDevice == null) {
273 return;
274 }
275 String address = btDevice.getAddress();
276 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
277 address = "";
278 }
279 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
280 "onBluetoothA2dpDeviceConfigChange addr=" + address));
281
282 final int a2dpCodec = btInfo.getCodec();
283
284 synchronized (mConnectedDevices) {
285 if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
286 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
287 "A2dp config change ignored"));
288 return;
289 }
290 final String key =
291 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
292 final DeviceInfo di = mConnectedDevices.get(key);
293 if (di == null) {
294 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
295 return;
296 }
297 // Device is connected
298 if (di.mDeviceCodecFormat != a2dpCodec) {
299 di.mDeviceCodecFormat = a2dpCodec;
300 mConnectedDevices.replace(key, di);
301 }
302 if (AudioService.DEBUG_DEVICES) {
303 Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
304 + di.mDeviceCodecFormat);
305 }
306 if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
307 btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) {
308 // force A2DP device disconnection in case of error so that AudioService state
309 // is consistent with audio policy manager state
310 final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
311 setBluetoothA2dpDeviceConnectionState(
312 btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
313 false /* suppressNoisyIntent */, musicDevice,
314 -1 /* a2dpVolume */);
315 }
316 }
317 }
318
319 /*package*/ void onBluetoothA2dpActiveDeviceChange(
320 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
321 final BluetoothDevice btDevice = btInfo.getBtDevice();
322 int a2dpVolume = btInfo.getVolume();
323 final int a2dpCodec = btInfo.getCodec();
324
325 if (AudioService.DEBUG_DEVICES) {
326 Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
327 }
328 String address = btDevice.getAddress();
329 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
330 address = "";
331 }
332 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
333 "onBluetoothA2dpActiveDeviceChange addr=" + address));
334
335 synchronized (mConnectedDevices) {
336 //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
337 // for this type of message
338 if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
339 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
340 "A2dp config change ignored"));
341 return;
342 }
343 final String key = DeviceInfo.makeDeviceListKey(
344 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
345 final DeviceInfo di = mConnectedDevices.get(key);
346 if (di == null) {
347 return;
348 }
349
350 // Device is connected
351 if (a2dpVolume != -1) {
352 final AudioService.VolumeStreamState streamState =
353 mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
354 // Convert index to internal representation in VolumeStreamState
355 a2dpVolume = a2dpVolume * 10;
356 streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
357 "onBluetoothA2dpActiveDeviceChange");
358 mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
359 }
360
361 if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
362 btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
363 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
364 // force A2DP device disconnection in case of error so that AudioService state is
365 // consistent with audio policy manager state
366 setBluetoothA2dpDeviceConnectionState(
367 btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
368 false /* suppressNoisyIntent */, musicDevice,
369 -1 /* a2dpVolume */);
370 }
371 }
372 }
373
374 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
375 synchronized (mConnectedDevices) {
376 makeA2dpDeviceUnavailableNow(address, a2dpCodec);
377 }
378 }
379
380 /*package*/ void onReportNewRoutes() {
381 int n = mRoutesObservers.beginBroadcast();
382 if (n > 0) {
383 AudioRoutesInfo routes;
384 synchronized (mCurAudioRoutes) {
385 routes = new AudioRoutesInfo(mCurAudioRoutes);
386 }
387 while (n > 0) {
388 n--;
389 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
390 try {
391 obs.dispatchAudioRoutesChanged(routes);
392 } catch (RemoteException e) { }
393 }
394 }
395 mRoutesObservers.finishBroadcast();
396 mDeviceBroker.observeDevicesForAllStreams();
397 }
398
399 private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
400 AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
401 | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
402
403 /*package*/ void onSetWiredDeviceConnectionState(
404 AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
405 AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
406
407 synchronized (mConnectedDevices) {
408 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
409 && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
410 mDeviceBroker.setBluetoothA2dpOnInt(true,
411 "onSetWiredDeviceConnectionState state DISCONNECTED");
412 }
413
414 if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress,
415 wdcs.mName)) {
416 // change of connection state failed, bailout
417 return;
418 }
419 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
420 if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
421 mDeviceBroker.setBluetoothA2dpOnInt(false,
422 "onSetWiredDeviceConnectionState state not DISCONNECTED");
423 }
424 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
425 }
426 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
427 sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
428 updateAudioRoutes(wdcs.mType, wdcs.mState);
429 }
430 }
431
432 /*package*/ void onToggleHdmi() {
433 synchronized (mConnectedDevices) {
434 // Is HDMI connected?
435 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
436 final DeviceInfo di = mConnectedDevices.get(key);
437 if (di == null) {
438 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
439 return;
440 }
441 // Toggle HDMI to retrigger broadcast with proper formats.
442 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
443 AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
444 "android"); // disconnect
445 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
446 AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
447 "android"); // reconnect
448 }
449 }
450 //------------------------------------------------------------
451 //
452
453 /**
454 * Implements the communication with AudioSystem to (dis)connect a device in the native layers
455 * @param connect true if connection
456 * @param device the device type
457 * @param address the address of the device
458 * @param deviceName human-readable name of device
459 * @return false if an error was reported by AudioSystem
460 */
461 /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
462 String deviceName) {
463 if (AudioService.DEBUG_DEVICES) {
464 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
465 + Integer.toHexString(device) + " address:" + address
466 + " name:" + deviceName + ")");
467 }
468 synchronized (mConnectedDevices) {
469 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
470 if (AudioService.DEBUG_DEVICES) {
471 Slog.i(TAG, "deviceKey:" + deviceKey);
472 }
473 DeviceInfo di = mConnectedDevices.get(deviceKey);
474 boolean isConnected = di != null;
475 if (AudioService.DEBUG_DEVICES) {
476 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
477 }
478 if (connect && !isConnected) {
479 final int res = AudioSystem.setDeviceConnectionState(device,
480 AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
481 AudioSystem.AUDIO_FORMAT_DEFAULT);
482 if (res != AudioSystem.AUDIO_STATUS_OK) {
483 Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
484 + " due to command error " + res);
485 return false;
486 }
487 mConnectedDevices.put(deviceKey, new DeviceInfo(
488 device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
489 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
490 return true;
491 } else if (!connect && isConnected) {
492 AudioSystem.setDeviceConnectionState(device,
493 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
494 AudioSystem.AUDIO_FORMAT_DEFAULT);
495 // always remove even if disconnection failed
496 mConnectedDevices.remove(deviceKey);
497 return true;
498 }
499 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
500 + ", deviceSpec=" + di + ", connect=" + connect);
501 }
502 return false;
503 }
504
505
506 /*package*/ void disconnectA2dp() {
507 synchronized (mConnectedDevices) {
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800508 final ArraySet<String> toRemove = new ArraySet<>();
509 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
510 mConnectedDevices.values().forEach(deviceInfo -> {
511 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
512 toRemove.add(deviceInfo.mDeviceAddress);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700513 }
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800514 });
515 if (toRemove.size() > 0) {
516 final int delay = checkSendBecomingNoisyIntentInt(
517 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
518 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
519 toRemove.stream().forEach(deviceAddress ->
520 makeA2dpDeviceUnavailableLater(deviceAddress, delay)
521 );
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700522 }
523 }
524 }
525
526 /*package*/ void disconnectA2dpSink() {
527 synchronized (mConnectedDevices) {
528 final ArraySet<String> toRemove = new ArraySet<>();
529 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
530 mConnectedDevices.values().forEach(deviceInfo -> {
531 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
532 toRemove.add(deviceInfo.mDeviceAddress);
533 }
534 });
535 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
536 }
537 }
538
539 /*package*/ void disconnectHearingAid() {
540 synchronized (mConnectedDevices) {
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800541 final ArraySet<String> toRemove = new ArraySet<>();
542 // Disconnect ALL DEVICE_OUT_HEARING_AID devices
543 mConnectedDevices.values().forEach(deviceInfo -> {
544 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
545 toRemove.add(deviceInfo.mDeviceAddress);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700546 }
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800547 });
548 if (toRemove.size() > 0) {
549 final int delay = checkSendBecomingNoisyIntentInt(
550 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
551 toRemove.stream().forEach(deviceAddress ->
552 // TODO delay not used?
553 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
554 );
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700555 }
556 }
557 }
558
559 // must be called before removing the device from mConnectedDevices
560 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
561 // from AudioSystem
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800562 /*package*/ int checkSendBecomingNoisyIntent(int device,
563 @AudioService.ConnectionState int state, int musicDevice) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700564 synchronized (mConnectedDevices) {
565 return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
566 }
567 }
568
569 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
570 synchronized (mCurAudioRoutes) {
571 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
572 mRoutesObservers.register(observer);
573 return routes;
574 }
575 }
576
577 /*package*/ AudioRoutesInfo getCurAudioRoutes() {
578 return mCurAudioRoutes;
579 }
580
Jean-Michel Trivifc86cfa2019-03-01 10:15:47 -0800581 /*package*/ void setBluetoothA2dpDeviceConnectionState(
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700582 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
583 int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
584 int delay;
585 if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
586 throw new IllegalArgumentException("invalid profile " + profile);
587 }
588 synchronized (mConnectedDevices) {
589 if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
590 int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
591 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
592 intState, musicDevice);
593 } else {
594 delay = 0;
595 }
596
597 final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
598
599 if (AudioService.DEBUG_DEVICES) {
600 Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
601 + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
602 + " suppressNoisyIntent: " + suppressNoisyIntent);
603 }
604
605 final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
606 new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
607 if (profile == BluetoothProfile.A2DP) {
608 mDeviceBroker.postA2dpSinkConnection(state,
609 a2dpDeviceInfo,
610 delay);
611 } else { //profile == BluetoothProfile.A2DP_SINK
612 mDeviceBroker.postA2dpSourceConnection(state,
613 a2dpDeviceInfo,
614 delay);
615 }
616 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700617 }
618
619 /*package*/ int handleBluetoothA2dpActiveDeviceChange(
620 @NonNull BluetoothDevice device,
621 @AudioService.BtProfileConnectionState int state, int profile,
622 boolean suppressNoisyIntent, int a2dpVolume) {
Jean-Michel Trivifc86cfa2019-03-01 10:15:47 -0800623 // method was added by QC but never used, and now conflicts with async behavior of
624 // handleBluetoothA2dpDeviceConfigChange and
625 // setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent
626 if (false) {
627 if (state == BluetoothProfile.STATE_DISCONNECTED) {
628 setBluetoothA2dpDeviceConnectionState(device, state, profile,
629 suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
630 }
631 // state == BluetoothProfile.STATE_CONNECTED
632 synchronized (mConnectedDevices) {
633 for (int i = 0; i < mConnectedDevices.size(); i++) {
634 final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i);
635 if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
636 continue;
637 }
638 // If A2DP device exists, this is either an active device change or
639 // device config change
640 final String existingDevicekey = mConnectedDevices.keyAt(i);
641 final String deviceName = device.getName();
642 final String address = device.getAddress();
643 final String newDeviceKey = DeviceInfo.makeDeviceListKey(
644 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
645 int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
646 // Device not equal to existing device, active device change
647 if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
648 mConnectedDevices.remove(existingDevicekey);
649 mConnectedDevices.put(newDeviceKey, new DeviceInfo(
650 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
651 address, a2dpCodec));
652 mDeviceBroker.postA2dpActiveDeviceChange(
653 new BtHelper.BluetoothA2dpDeviceInfo(
654 device, a2dpVolume, a2dpCodec));
655 return 0;
656 } else {
657 // Device config change for existing device
658 mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
659 return 0;
660 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700661 }
662 }
663 }
664 return 0;
665 }
666
667 /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
668 String address, String name, String caller) {
669 synchronized (mConnectedDevices) {
670 int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
671 mDeviceBroker.postSetWiredDeviceConnectionState(
672 new WiredDeviceConnectionState(type, state, address, name, caller),
673 delay);
674 return delay;
675 }
676 }
677
678 /*package*/ int setBluetoothHearingAidDeviceConnectionState(
679 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
680 boolean suppressNoisyIntent, int musicDevice) {
681 int delay;
682 synchronized (mConnectedDevices) {
683 if (!suppressNoisyIntent) {
684 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
685 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
686 intState, musicDevice);
687 } else {
688 delay = 0;
689 }
690 mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
691 return delay;
692 }
693 }
694
695
696 //-------------------------------------------------------------------
697 // Internal utilities
698
699 @GuardedBy("mConnectedDevices")
700 private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
701 int a2dpCodec) {
702 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
703 // audio policy manager
704 AudioService.VolumeStreamState streamState =
705 mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
706 mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
707 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
708 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
709 // Reset A2DP suspend state each time a new sink is connected
710 AudioSystem.setParameters("A2dpSuspended=false");
711 mConnectedDevices.put(
712 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
713 new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
714 address, a2dpCodec));
715 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
716 setCurrentAudioRouteNameIfPossible(name);
717 }
718
719 @GuardedBy("mConnectedDevices")
720 private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
721 if (address == null) {
722 return;
723 }
724 mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
725 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
726 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
727 mConnectedDevices.remove(
728 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
729 // Remove A2DP routes as well
730 setCurrentAudioRouteNameIfPossible(null);
731 if (mDockAddress == address) {
732 mDockAddress = null;
733 }
734 }
735
736 @GuardedBy("mConnectedDevices")
737 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
738 // prevent any activity on the A2DP audio output to avoid unwanted
739 // reconnection of the sink.
740 AudioSystem.setParameters("A2dpSuspended=true");
741 // retrieve DeviceInfo before removing device
742 final String deviceKey =
743 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
744 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
745 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
746 AudioSystem.AUDIO_FORMAT_DEFAULT;
747 // the device will be made unavailable later, so consider it disconnected right away
748 mConnectedDevices.remove(deviceKey);
749 // send the delayed message to make the device unavailable later
750 mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
751 }
752
753
754 @GuardedBy("mConnectedDevices")
755 private void makeA2dpSrcAvailable(String address) {
756 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
757 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
758 AudioSystem.AUDIO_FORMAT_DEFAULT);
759 mConnectedDevices.put(
760 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
761 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
762 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
763 }
764
765 @GuardedBy("mConnectedDevices")
766 private void makeA2dpSrcUnavailable(String address) {
767 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
768 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
769 AudioSystem.AUDIO_FORMAT_DEFAULT);
770 mConnectedDevices.remove(
771 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
772 }
773
774 @GuardedBy("mConnectedDevices")
775 private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
776 final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC)
777 .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
778 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC);
779
780 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
781 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
782 AudioSystem.AUDIO_FORMAT_DEFAULT);
783 mConnectedDevices.put(
784 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
785 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
786 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
787 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
788 mDeviceBroker.setDeviceVolume(
789 mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC),
790 AudioSystem.DEVICE_OUT_HEARING_AID);
791 setCurrentAudioRouteNameIfPossible(name);
792 }
793
794 @GuardedBy("mConnectedDevices")
795 private void makeHearingAidDeviceUnavailable(String address) {
796 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
797 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
798 AudioSystem.AUDIO_FORMAT_DEFAULT);
799 mConnectedDevices.remove(
800 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
801 // Remove Hearing Aid routes as well
802 setCurrentAudioRouteNameIfPossible(null);
803 }
804
805 @GuardedBy("mConnectedDevices")
806 private void setCurrentAudioRouteNameIfPossible(String name) {
807 synchronized (mCurAudioRoutes) {
808 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
809 return;
810 }
811 if (name != null || !isCurrentDeviceConnected()) {
812 mCurAudioRoutes.bluetoothName = name;
813 mDeviceBroker.postReportNewRoutes();
814 }
815 }
816 }
817
818 @GuardedBy("mConnectedDevices")
819 private boolean isCurrentDeviceConnected() {
820 return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
821 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
822 }
823
824 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
825 // sent if:
826 // - none of these devices are connected anymore after one is disconnected AND
827 // - the device being disconnected is actually used for music.
828 // Access synchronized on mConnectedDevices
829 private int mBecomingNoisyIntentDevices =
830 AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
831 | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
832 | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
833 | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
834 | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
835 | AudioSystem.DEVICE_OUT_HEARING_AID;
836
837 // must be called before removing the device from mConnectedDevices
838 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
839 // from AudioSystem
840 @GuardedBy("mConnectedDevices")
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800841 private int checkSendBecomingNoisyIntentInt(int device,
842 @AudioService.ConnectionState int state, int musicDevice) {
843 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700844 return 0;
845 }
846 if ((device & mBecomingNoisyIntentDevices) == 0) {
847 return 0;
848 }
849 int delay = 0;
850 int devices = 0;
851 for (int i = 0; i < mConnectedDevices.size(); i++) {
852 int dev = mConnectedDevices.valueAt(i).mDeviceType;
853 if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
854 && ((dev & mBecomingNoisyIntentDevices) != 0)) {
855 devices |= dev;
856 }
857 }
858 if (musicDevice == AudioSystem.DEVICE_NONE) {
859 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
860 }
861 // ignore condition on device being actually used for music when in communication
862 // because music routing is altered in this case.
863 // also checks whether media routing if affected by a dynamic policy
864 if (((device == musicDevice) || mDeviceBroker.isInCommunication())
865 && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) {
Jean-Michel Trivib9465152019-02-06 15:20:19 -0800866 mDeviceBroker.postBroadcastBecomingNoisy();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700867 delay = 1000;
868 }
869
870 return delay;
871 }
872
873 // Intent "extra" data keys.
874 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
875 private static final String CONNECT_INTENT_KEY_STATE = "state";
876 private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
877 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
878 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
879 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
880 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
881
882 private void sendDeviceConnectionIntent(int device, int state, String address,
883 String deviceName) {
884 if (AudioService.DEBUG_DEVICES) {
885 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
886 + " state:0x" + Integer.toHexString(state) + " address:" + address
887 + " name:" + deviceName + ");");
888 }
889 Intent intent = new Intent();
890
891 switch(device) {
892 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
893 intent.setAction(Intent.ACTION_HEADSET_PLUG);
894 intent.putExtra("microphone", 1);
895 break;
896 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
897 case AudioSystem.DEVICE_OUT_LINE:
898 intent.setAction(Intent.ACTION_HEADSET_PLUG);
899 intent.putExtra("microphone", 0);
900 break;
901 case AudioSystem.DEVICE_OUT_USB_HEADSET:
902 intent.setAction(Intent.ACTION_HEADSET_PLUG);
903 intent.putExtra("microphone",
904 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
905 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
906 break;
907 case AudioSystem.DEVICE_IN_USB_HEADSET:
908 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
909 == AudioSystem.DEVICE_STATE_AVAILABLE) {
910 intent.setAction(Intent.ACTION_HEADSET_PLUG);
911 intent.putExtra("microphone", 1);
912 } else {
913 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
914 return;
915 }
916 break;
917 case AudioSystem.DEVICE_OUT_HDMI:
918 case AudioSystem.DEVICE_OUT_HDMI_ARC:
919 configureHdmiPlugIntent(intent, state);
920 break;
921 }
922
923 if (intent.getAction() == null) {
924 return;
925 }
926
927 intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
928 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
929 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
930
931 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
932
933 final long ident = Binder.clearCallingIdentity();
934 try {
935 ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
936 } finally {
937 Binder.restoreCallingIdentity(ident);
938 }
939 }
940
941 private void updateAudioRoutes(int device, int state) {
942 int connType = 0;
943
944 switch (device) {
945 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
946 connType = AudioRoutesInfo.MAIN_HEADSET;
947 break;
948 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
949 case AudioSystem.DEVICE_OUT_LINE:
950 connType = AudioRoutesInfo.MAIN_HEADPHONES;
951 break;
952 case AudioSystem.DEVICE_OUT_HDMI:
953 case AudioSystem.DEVICE_OUT_HDMI_ARC:
954 connType = AudioRoutesInfo.MAIN_HDMI;
955 break;
956 case AudioSystem.DEVICE_OUT_USB_DEVICE:
957 case AudioSystem.DEVICE_OUT_USB_HEADSET:
958 connType = AudioRoutesInfo.MAIN_USB;
959 break;
960 }
961
962 synchronized (mCurAudioRoutes) {
963 if (connType == 0) {
964 return;
965 }
966 int newConn = mCurAudioRoutes.mainType;
967 if (state != 0) {
968 newConn |= connType;
969 } else {
970 newConn &= ~connType;
971 }
972 if (newConn != mCurAudioRoutes.mainType) {
973 mCurAudioRoutes.mainType = newConn;
974 mDeviceBroker.postReportNewRoutes();
975 }
976 }
977 }
978
979 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
980 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
981 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
982 if (state != AudioService.CONNECTION_STATE_CONNECTED) {
983 return;
984 }
985 ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
986 int[] portGeneration = new int[1];
987 int status = AudioSystem.listAudioPorts(ports, portGeneration);
988 if (status != AudioManager.SUCCESS) {
989 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
990 return;
991 }
992 for (AudioPort port : ports) {
993 if (!(port instanceof AudioDevicePort)) {
994 continue;
995 }
996 final AudioDevicePort devicePort = (AudioDevicePort) port;
997 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
998 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
999 continue;
1000 }
1001 // found an HDMI port: format the list of supported encodings
1002 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
1003 if (formats.length > 0) {
1004 ArrayList<Integer> encodingList = new ArrayList(1);
1005 for (int format : formats) {
1006 // a format in the list can be 0, skip it
1007 if (format != AudioFormat.ENCODING_INVALID) {
1008 encodingList.add(format);
1009 }
1010 }
1011 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
1012 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
1013 }
1014 // find the maximum supported number of channels
1015 int maxChannels = 0;
1016 for (int mask : devicePort.channelMasks()) {
1017 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
1018 if (channelCount > maxChannels) {
1019 maxChannels = channelCount;
1020 }
1021 }
1022 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
1023 }
1024 }
1025}