blob: 90973a888a9d20d1caf93e8c1b4e2f8544b95ed1 [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;
Jean-Michel Trividc552e92019-06-24 10:39:19 -070044import com.android.internal.annotations.VisibleForTesting;
Jean-Michel Trivi58850372018-09-14 16:01:28 -070045
46import java.util.ArrayList;
47
48/**
49 * Class to manage the inventory of all connected devices.
50 * This class is thread-safe.
Jean-Michel Trividc552e92019-06-24 10:39:19 -070051 * (non final for mocking/spying)
Jean-Michel Trivi58850372018-09-14 16:01:28 -070052 */
Jean-Michel Trividc552e92019-06-24 10:39:19 -070053public class AudioDeviceInventory {
Jean-Michel Trivi58850372018-09-14 16:01:28 -070054
55 private static final String TAG = "AS.AudioDeviceInventory";
56
57 // Actual list of connected devices
58 // Key for map created from DeviceInfo.makeDeviceListKey()
59 private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
60
Jean-Michel Trividc552e92019-06-24 10:39:19 -070061 private @NonNull AudioDeviceBroker mDeviceBroker;
Jean-Michel Trivi58850372018-09-14 16:01:28 -070062
63 // cache of the address of the last dock the device was connected to
64 private String mDockAddress;
65
66 // Monitoring of audio routes. Protected by mAudioRoutes.
67 final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
68 final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
69 new RemoteCallbackList<IAudioRoutesObserver>();
70
Jean-Michel Trividc552e92019-06-24 10:39:19 -070071 /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
72 mDeviceBroker = broker;
73 }
74
75 //-----------------------------------------------------------
76 /** for mocking only */
77 /*package*/ AudioDeviceInventory() {
78 mDeviceBroker = null;
79 }
80
81 /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
82 mDeviceBroker = broker;
83 }
84
Jean-Michel Trivi58850372018-09-14 16:01:28 -070085 //------------------------------------------------------------
86 /**
87 * Class to store info about connected devices.
88 * Use makeDeviceListKey() to make a unique key for this list.
89 */
90 private static class DeviceInfo {
91 final int mDeviceType;
92 final String mDeviceName;
93 final String mDeviceAddress;
94 int mDeviceCodecFormat;
95
96 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
97 mDeviceType = deviceType;
98 mDeviceName = deviceName;
99 mDeviceAddress = deviceAddress;
100 mDeviceCodecFormat = deviceCodecFormat;
101 }
102
103 @Override
104 public String toString() {
105 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
106 + " name:" + mDeviceName
107 + " addr:" + mDeviceAddress
108 + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
109 }
110
111 /**
112 * Generate a unique key for the mConnectedDevices List by composing the device "type"
113 * and the "address" associated with a specific instance of that device type
114 */
115 private static String makeDeviceListKey(int device, String deviceAddress) {
116 return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
117 }
118 }
119
120 /**
121 * A class just for packaging up a set of connection parameters.
122 */
123 /*package*/ class WiredDeviceConnectionState {
124 public final int mType;
125 public final @AudioService.ConnectionState int mState;
126 public final String mAddress;
127 public final String mName;
128 public final String mCaller;
129
130 /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
131 String address, String name, String caller) {
132 mType = type;
133 mState = state;
134 mAddress = address;
135 mName = name;
136 mCaller = caller;
137 }
138 }
139
140 //------------------------------------------------------------
141 // Message handling from AudioDeviceBroker
142
143 /**
144 * Restore previously connected devices. Use in case of audio server crash
145 * (see AudioService.onAudioServerDied() method)
146 */
147 /*package*/ void onRestoreDevices() {
148 synchronized (mConnectedDevices) {
149 for (int i = 0; i < mConnectedDevices.size(); i++) {
150 DeviceInfo di = mConnectedDevices.valueAt(i);
151 AudioSystem.setDeviceConnectionState(
152 di.mDeviceType,
153 AudioSystem.DEVICE_STATE_AVAILABLE,
154 di.mDeviceAddress,
155 di.mDeviceName,
156 di.mDeviceCodecFormat);
157 }
158 }
159 }
160
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700161 // only public for mocking/spying
Jean-Michel Trivi2578b392019-10-29 16:34:42 +0000162 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700163 @VisibleForTesting
164 public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800165 @AudioService.BtProfileConnectionState int state) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700166 final BluetoothDevice btDevice = btInfo.getBtDevice();
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800167 int a2dpVolume = btInfo.getVolume();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700168 if (AudioService.DEBUG_DEVICES) {
169 Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800170 + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700171 }
172 String address = btDevice.getAddress();
173 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
174 address = "";
175 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700176
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800177 final int a2dpCodec = btInfo.getCodec();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700178
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700179 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
180 "A2DP sink connected: device addr=" + address + " state=" + state
181 + " codec=" + a2dpCodec
182 + " vol=" + a2dpVolume));
183
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700184 synchronized (mConnectedDevices) {
185 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
186 btDevice.getAddress());
187 final DeviceInfo di = mConnectedDevices.get(key);
188 boolean isConnected = di != null;
189
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700190 if (isConnected) {
191 if (state == BluetoothProfile.STATE_CONNECTED) {
192 // device is already connected, but we are receiving a connection again,
193 // it could be for a codec change
194 if (a2dpCodec != di.mDeviceCodecFormat) {
195 mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700196 }
197 } else {
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700198 if (btDevice.isBluetoothDock()) {
199 if (state == BluetoothProfile.STATE_DISCONNECTED) {
200 // introduction of a delay for transient disconnections of docks when
201 // power is rapidly turned off/on, this message will be canceled if
202 // we reconnect the dock under a preset delay
203 makeA2dpDeviceUnavailableLater(address,
204 AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
205 // the next time isConnected is evaluated, it will be false for the dock
206 }
207 } else {
208 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
209 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700210 }
211 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
212 if (btDevice.isBluetoothDock()) {
213 // this could be a reconnection after a transient disconnection
214 mDeviceBroker.cancelA2dpDockTimeout();
215 mDockAddress = address;
216 } else {
217 // this could be a connection of another A2DP device before the timeout of
218 // a dock: cancel the dock timeout, and make the dock unavailable now
219 if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
220 mDeviceBroker.cancelA2dpDockTimeout();
221 makeA2dpDeviceUnavailableNow(mDockAddress,
222 AudioSystem.AUDIO_FORMAT_DEFAULT);
223 }
224 }
225 if (a2dpVolume != -1) {
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700226 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
227 // convert index to internal representation in VolumeStreamState
228 a2dpVolume * 10,
229 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700230 }
Jack Hecb499642019-04-03 00:48:49 -0700231 makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700232 "onSetA2dpSinkConnectionState", a2dpCodec);
233 }
234 }
235 }
236
237 /*package*/ void onSetA2dpSourceConnectionState(
238 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
239 final BluetoothDevice btDevice = btInfo.getBtDevice();
240 if (AudioService.DEBUG_DEVICES) {
241 Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
242 + state);
243 }
244 String address = btDevice.getAddress();
245 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
246 address = "";
247 }
248
249 synchronized (mConnectedDevices) {
250 final String key = DeviceInfo.makeDeviceListKey(
251 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
252 final DeviceInfo di = mConnectedDevices.get(key);
253 boolean isConnected = di != null;
254
255 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
256 makeA2dpSrcUnavailable(address);
257 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
258 makeA2dpSrcAvailable(address);
259 }
260 }
261 }
262
263 /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
Eric Laurent0cf98c72019-04-26 14:41:32 -0700264 @AudioService.BtProfileConnectionState int state, int streamType) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700265 String address = btDevice.getAddress();
266 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
267 address = "";
268 }
269 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
270 "onSetHearingAidConnectionState addr=" + address));
271
272 synchronized (mConnectedDevices) {
273 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
274 btDevice.getAddress());
275 final DeviceInfo di = mConnectedDevices.get(key);
276 boolean isConnected = di != null;
277
278 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
279 makeHearingAidDeviceUnavailable(address);
280 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
Eric Laurent0cf98c72019-04-26 14:41:32 -0700281 makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700282 "onSetHearingAidConnectionState");
283 }
284 }
285 }
286
Jean-Michel Trivi2578b392019-10-29 16:34:42 +0000287 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800288 /*package*/ void onBluetoothA2dpActiveDeviceChange(
289 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700290 final BluetoothDevice btDevice = btInfo.getBtDevice();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700291 if (btDevice == null) {
292 return;
293 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700294 if (AudioService.DEBUG_DEVICES) {
295 Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
296 }
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800297 int a2dpVolume = btInfo.getVolume();
298 final int a2dpCodec = btInfo.getCodec();
299
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700300 String address = btDevice.getAddress();
301 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
302 address = "";
303 }
304 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800305 "onBluetoothA2dpActiveDeviceChange addr=" + address
306 + " event=" + BtHelper.a2dpDeviceEventToString(event)));
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700307
308 synchronized (mConnectedDevices) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700309 if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
310 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700311 "A2dp config change ignored (scheduled connection change)"));
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700312 return;
313 }
314 final String key = DeviceInfo.makeDeviceListKey(
315 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
316 final DeviceInfo di = mConnectedDevices.get(key);
317 if (di == null) {
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800318 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange");
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700319 return;
320 }
321
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800322 if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
323 // Device is connected
324 if (a2dpVolume != -1) {
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700325 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
326 // convert index to internal representation in VolumeStreamState
327 a2dpVolume * 10,
328 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800329 "onBluetoothA2dpActiveDeviceChange");
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800330 }
331 } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
332 if (di.mDeviceCodecFormat != a2dpCodec) {
333 di.mDeviceCodecFormat = a2dpCodec;
334 mConnectedDevices.replace(key, di);
335 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700336 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700337 if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
Jack Hecb499642019-04-03 00:48:49 -0700338 BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700339 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
340 // force A2DP device disconnection in case of error so that AudioService state is
341 // consistent with audio policy manager state
342 setBluetoothA2dpDeviceConnectionState(
343 btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
344 false /* suppressNoisyIntent */, musicDevice,
345 -1 /* a2dpVolume */);
346 }
347 }
348 }
349
350 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
351 synchronized (mConnectedDevices) {
352 makeA2dpDeviceUnavailableNow(address, a2dpCodec);
353 }
354 }
355
356 /*package*/ void onReportNewRoutes() {
357 int n = mRoutesObservers.beginBroadcast();
358 if (n > 0) {
359 AudioRoutesInfo routes;
360 synchronized (mCurAudioRoutes) {
361 routes = new AudioRoutesInfo(mCurAudioRoutes);
362 }
363 while (n > 0) {
364 n--;
365 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
366 try {
367 obs.dispatchAudioRoutesChanged(routes);
368 } catch (RemoteException e) { }
369 }
370 }
371 mRoutesObservers.finishBroadcast();
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700372 mDeviceBroker.postObserveDevicesForAllStreams();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700373 }
374
375 private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
376 AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
377 | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
378
379 /*package*/ void onSetWiredDeviceConnectionState(
380 AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
381 AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
382
383 synchronized (mConnectedDevices) {
384 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
385 && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
386 mDeviceBroker.setBluetoothA2dpOnInt(true,
387 "onSetWiredDeviceConnectionState state DISCONNECTED");
388 }
389
Jean-Michel Trivi9c7d2b02019-04-25 11:47:15 -0700390 if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
391 wdcs.mType, wdcs.mAddress, wdcs.mName)) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700392 // change of connection state failed, bailout
393 return;
394 }
395 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
396 if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
397 mDeviceBroker.setBluetoothA2dpOnInt(false,
398 "onSetWiredDeviceConnectionState state not DISCONNECTED");
399 }
400 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
401 }
Jean-Michel Trivi9c7d2b02019-04-25 11:47:15 -0700402 if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
403 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
404 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700405 sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
406 updateAudioRoutes(wdcs.mType, wdcs.mState);
407 }
408 }
409
410 /*package*/ void onToggleHdmi() {
411 synchronized (mConnectedDevices) {
412 // Is HDMI connected?
413 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
414 final DeviceInfo di = mConnectedDevices.get(key);
415 if (di == null) {
416 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
417 return;
418 }
419 // Toggle HDMI to retrigger broadcast with proper formats.
420 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
421 AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
422 "android"); // disconnect
423 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
424 AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
425 "android"); // reconnect
426 }
427 }
428 //------------------------------------------------------------
429 //
430
431 /**
432 * Implements the communication with AudioSystem to (dis)connect a device in the native layers
433 * @param connect true if connection
434 * @param device the device type
435 * @param address the address of the device
436 * @param deviceName human-readable name of device
437 * @return false if an error was reported by AudioSystem
438 */
439 /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
440 String deviceName) {
441 if (AudioService.DEBUG_DEVICES) {
442 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
443 + Integer.toHexString(device) + " address:" + address
444 + " name:" + deviceName + ")");
445 }
446 synchronized (mConnectedDevices) {
447 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
448 if (AudioService.DEBUG_DEVICES) {
449 Slog.i(TAG, "deviceKey:" + deviceKey);
450 }
451 DeviceInfo di = mConnectedDevices.get(deviceKey);
452 boolean isConnected = di != null;
453 if (AudioService.DEBUG_DEVICES) {
454 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
455 }
456 if (connect && !isConnected) {
457 final int res = AudioSystem.setDeviceConnectionState(device,
458 AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
459 AudioSystem.AUDIO_FORMAT_DEFAULT);
460 if (res != AudioSystem.AUDIO_STATUS_OK) {
461 Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
462 + " due to command error " + res);
463 return false;
464 }
465 mConnectedDevices.put(deviceKey, new DeviceInfo(
466 device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
467 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
468 return true;
469 } else if (!connect && isConnected) {
470 AudioSystem.setDeviceConnectionState(device,
471 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
472 AudioSystem.AUDIO_FORMAT_DEFAULT);
473 // always remove even if disconnection failed
474 mConnectedDevices.remove(deviceKey);
475 return true;
476 }
477 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
478 + ", deviceSpec=" + di + ", connect=" + connect);
479 }
480 return false;
481 }
482
483
484 /*package*/ void disconnectA2dp() {
485 synchronized (mConnectedDevices) {
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800486 final ArraySet<String> toRemove = new ArraySet<>();
487 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
488 mConnectedDevices.values().forEach(deviceInfo -> {
489 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
490 toRemove.add(deviceInfo.mDeviceAddress);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700491 }
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800492 });
493 if (toRemove.size() > 0) {
494 final int delay = checkSendBecomingNoisyIntentInt(
495 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
496 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
497 toRemove.stream().forEach(deviceAddress ->
498 makeA2dpDeviceUnavailableLater(deviceAddress, delay)
499 );
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700500 }
501 }
502 }
503
504 /*package*/ void disconnectA2dpSink() {
505 synchronized (mConnectedDevices) {
506 final ArraySet<String> toRemove = new ArraySet<>();
507 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
508 mConnectedDevices.values().forEach(deviceInfo -> {
509 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
510 toRemove.add(deviceInfo.mDeviceAddress);
511 }
512 });
513 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
514 }
515 }
516
517 /*package*/ void disconnectHearingAid() {
518 synchronized (mConnectedDevices) {
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800519 final ArraySet<String> toRemove = new ArraySet<>();
520 // Disconnect ALL DEVICE_OUT_HEARING_AID devices
521 mConnectedDevices.values().forEach(deviceInfo -> {
522 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
523 toRemove.add(deviceInfo.mDeviceAddress);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700524 }
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800525 });
526 if (toRemove.size() > 0) {
527 final int delay = checkSendBecomingNoisyIntentInt(
528 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
529 toRemove.stream().forEach(deviceAddress ->
530 // TODO delay not used?
531 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
532 );
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700533 }
534 }
535 }
536
537 // must be called before removing the device from mConnectedDevices
538 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
539 // from AudioSystem
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800540 /*package*/ int checkSendBecomingNoisyIntent(int device,
541 @AudioService.ConnectionState int state, int musicDevice) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700542 synchronized (mConnectedDevices) {
543 return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
544 }
545 }
546
547 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
548 synchronized (mCurAudioRoutes) {
549 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
550 mRoutesObservers.register(observer);
551 return routes;
552 }
553 }
554
555 /*package*/ AudioRoutesInfo getCurAudioRoutes() {
556 return mCurAudioRoutes;
557 }
558
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700559 // only public for mocking/spying
Jean-Michel Trivi2578b392019-10-29 16:34:42 +0000560 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700561 @VisibleForTesting
562 public void setBluetoothA2dpDeviceConnectionState(
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700563 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
564 int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
565 int delay;
566 if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
567 throw new IllegalArgumentException("invalid profile " + profile);
568 }
569 synchronized (mConnectedDevices) {
570 if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700571 @AudioService.ConnectionState int asState =
572 (state == BluetoothA2dp.STATE_CONNECTED)
573 ? AudioService.CONNECTION_STATE_CONNECTED
574 : AudioService.CONNECTION_STATE_DISCONNECTED;
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700575 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700576 asState, musicDevice);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700577 } else {
578 delay = 0;
579 }
580
581 final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
582
583 if (AudioService.DEBUG_DEVICES) {
584 Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
585 + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
586 + " suppressNoisyIntent: " + suppressNoisyIntent);
587 }
588
589 final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
590 new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
591 if (profile == BluetoothProfile.A2DP) {
Jean-Michel Trivif7345252019-05-29 14:25:02 -0700592 if (delay == 0) {
593 onSetA2dpSinkConnectionState(a2dpDeviceInfo, state);
594 } else {
595 mDeviceBroker.postA2dpSinkConnection(state,
596 a2dpDeviceInfo,
597 delay);
598 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700599 } else { //profile == BluetoothProfile.A2DP_SINK
600 mDeviceBroker.postA2dpSourceConnection(state,
601 a2dpDeviceInfo,
602 delay);
603 }
604 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700605 }
606
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700607 /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
608 String address, String name, String caller) {
609 synchronized (mConnectedDevices) {
610 int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
611 mDeviceBroker.postSetWiredDeviceConnectionState(
612 new WiredDeviceConnectionState(type, state, address, name, caller),
613 delay);
614 return delay;
615 }
616 }
617
618 /*package*/ int setBluetoothHearingAidDeviceConnectionState(
619 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
620 boolean suppressNoisyIntent, int musicDevice) {
621 int delay;
622 synchronized (mConnectedDevices) {
623 if (!suppressNoisyIntent) {
624 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
625 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
626 intState, musicDevice);
627 } else {
628 delay = 0;
629 }
630 mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
631 return delay;
632 }
633 }
634
635
636 //-------------------------------------------------------------------
637 // Internal utilities
638
639 @GuardedBy("mConnectedDevices")
640 private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
641 int a2dpCodec) {
642 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
643 // audio policy manager
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700644 mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
645 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
646 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
647 // Reset A2DP suspend state each time a new sink is connected
648 AudioSystem.setParameters("A2dpSuspended=false");
649 mConnectedDevices.put(
650 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
651 new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
652 address, a2dpCodec));
653 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
654 setCurrentAudioRouteNameIfPossible(name);
655 }
656
657 @GuardedBy("mConnectedDevices")
658 private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
659 if (address == null) {
660 return;
661 }
662 mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
663 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
664 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
665 mConnectedDevices.remove(
666 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
667 // Remove A2DP routes as well
668 setCurrentAudioRouteNameIfPossible(null);
669 if (mDockAddress == address) {
670 mDockAddress = null;
671 }
672 }
673
674 @GuardedBy("mConnectedDevices")
675 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
676 // prevent any activity on the A2DP audio output to avoid unwanted
677 // reconnection of the sink.
678 AudioSystem.setParameters("A2dpSuspended=true");
679 // retrieve DeviceInfo before removing device
680 final String deviceKey =
681 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
682 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
683 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
684 AudioSystem.AUDIO_FORMAT_DEFAULT;
685 // the device will be made unavailable later, so consider it disconnected right away
686 mConnectedDevices.remove(deviceKey);
687 // send the delayed message to make the device unavailable later
688 mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
689 }
690
691
692 @GuardedBy("mConnectedDevices")
693 private void makeA2dpSrcAvailable(String address) {
694 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
695 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
696 AudioSystem.AUDIO_FORMAT_DEFAULT);
697 mConnectedDevices.put(
698 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
699 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
700 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
701 }
702
703 @GuardedBy("mConnectedDevices")
704 private void makeA2dpSrcUnavailable(String address) {
705 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
706 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
707 AudioSystem.AUDIO_FORMAT_DEFAULT);
708 mConnectedDevices.remove(
709 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
710 }
711
712 @GuardedBy("mConnectedDevices")
Eric Laurent0cf98c72019-04-26 14:41:32 -0700713 private void makeHearingAidDeviceAvailable(
714 String address, String name, int streamType, String eventSource) {
715 final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700716 AudioSystem.DEVICE_OUT_HEARING_AID);
Eric Laurent0cf98c72019-04-26 14:41:32 -0700717 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700718
719 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
720 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
721 AudioSystem.AUDIO_FORMAT_DEFAULT);
722 mConnectedDevices.put(
723 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
724 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
725 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
726 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
Eric Laurent0cf98c72019-04-26 14:41:32 -0700727 mDeviceBroker.postApplyVolumeOnDevice(streamType,
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700728 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700729 setCurrentAudioRouteNameIfPossible(name);
730 }
731
732 @GuardedBy("mConnectedDevices")
733 private void makeHearingAidDeviceUnavailable(String address) {
734 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
735 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
736 AudioSystem.AUDIO_FORMAT_DEFAULT);
737 mConnectedDevices.remove(
738 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
739 // Remove Hearing Aid routes as well
740 setCurrentAudioRouteNameIfPossible(null);
741 }
742
743 @GuardedBy("mConnectedDevices")
744 private void setCurrentAudioRouteNameIfPossible(String name) {
745 synchronized (mCurAudioRoutes) {
746 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
747 return;
748 }
749 if (name != null || !isCurrentDeviceConnected()) {
750 mCurAudioRoutes.bluetoothName = name;
751 mDeviceBroker.postReportNewRoutes();
752 }
753 }
754 }
755
756 @GuardedBy("mConnectedDevices")
757 private boolean isCurrentDeviceConnected() {
758 return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
759 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
760 }
761
762 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
763 // sent if:
764 // - none of these devices are connected anymore after one is disconnected AND
765 // - the device being disconnected is actually used for music.
766 // Access synchronized on mConnectedDevices
767 private int mBecomingNoisyIntentDevices =
768 AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
769 | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
770 | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
771 | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
772 | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
773 | AudioSystem.DEVICE_OUT_HEARING_AID;
774
775 // must be called before removing the device from mConnectedDevices
776 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
777 // from AudioSystem
778 @GuardedBy("mConnectedDevices")
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800779 private int checkSendBecomingNoisyIntentInt(int device,
780 @AudioService.ConnectionState int state, int musicDevice) {
781 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700782 return 0;
783 }
784 if ((device & mBecomingNoisyIntentDevices) == 0) {
785 return 0;
786 }
787 int delay = 0;
788 int devices = 0;
789 for (int i = 0; i < mConnectedDevices.size(); i++) {
790 int dev = mConnectedDevices.valueAt(i).mDeviceType;
791 if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
792 && ((dev & mBecomingNoisyIntentDevices) != 0)) {
793 devices |= dev;
794 }
795 }
796 if (musicDevice == AudioSystem.DEVICE_NONE) {
797 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
798 }
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800799
800 // always ignore condition on device being actually used for music when in communication
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700801 // because music routing is altered in this case.
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800802 // also checks whether media routing if affected by a dynamic policy or mirroring
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700803 if (((device == musicDevice) || mDeviceBroker.isInCommunication())
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800804 && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()
805 && ((musicDevice & AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) == 0)) {
Jean-Michel Trivic1adecd2019-04-13 17:11:50 -0700806 if (!AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
807 && !mDeviceBroker.hasAudioFocusUsers()) {
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800808 // no media playback, not a "becoming noisy" situation, otherwise it could cause
809 // the pausing of some apps that are playing remotely
810 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
Jean-Michel Trivic1adecd2019-04-13 17:11:50 -0700811 "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800812 return 0;
813 }
Jean-Michel Trivib9465152019-02-06 15:20:19 -0800814 mDeviceBroker.postBroadcastBecomingNoisy();
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700815 delay = AudioService.BECOMING_NOISY_DELAY_MS;
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700816 }
817
818 return delay;
819 }
820
821 // Intent "extra" data keys.
822 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
823 private static final String CONNECT_INTENT_KEY_STATE = "state";
824 private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
825 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
826 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
827 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
828 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
829
830 private void sendDeviceConnectionIntent(int device, int state, String address,
831 String deviceName) {
832 if (AudioService.DEBUG_DEVICES) {
833 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
834 + " state:0x" + Integer.toHexString(state) + " address:" + address
835 + " name:" + deviceName + ");");
836 }
837 Intent intent = new Intent();
838
839 switch(device) {
840 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
841 intent.setAction(Intent.ACTION_HEADSET_PLUG);
842 intent.putExtra("microphone", 1);
843 break;
844 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
845 case AudioSystem.DEVICE_OUT_LINE:
846 intent.setAction(Intent.ACTION_HEADSET_PLUG);
847 intent.putExtra("microphone", 0);
848 break;
849 case AudioSystem.DEVICE_OUT_USB_HEADSET:
850 intent.setAction(Intent.ACTION_HEADSET_PLUG);
851 intent.putExtra("microphone",
852 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
853 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
854 break;
855 case AudioSystem.DEVICE_IN_USB_HEADSET:
856 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
857 == AudioSystem.DEVICE_STATE_AVAILABLE) {
858 intent.setAction(Intent.ACTION_HEADSET_PLUG);
859 intent.putExtra("microphone", 1);
860 } else {
861 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
862 return;
863 }
864 break;
865 case AudioSystem.DEVICE_OUT_HDMI:
866 case AudioSystem.DEVICE_OUT_HDMI_ARC:
867 configureHdmiPlugIntent(intent, state);
868 break;
869 }
870
871 if (intent.getAction() == null) {
872 return;
873 }
874
875 intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
876 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
877 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
878
879 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
880
881 final long ident = Binder.clearCallingIdentity();
882 try {
Paul McLeanb72445e2019-03-18 14:36:32 -0600883 ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700884 } finally {
885 Binder.restoreCallingIdentity(ident);
886 }
887 }
888
889 private void updateAudioRoutes(int device, int state) {
890 int connType = 0;
891
892 switch (device) {
893 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
894 connType = AudioRoutesInfo.MAIN_HEADSET;
895 break;
896 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
897 case AudioSystem.DEVICE_OUT_LINE:
898 connType = AudioRoutesInfo.MAIN_HEADPHONES;
899 break;
900 case AudioSystem.DEVICE_OUT_HDMI:
901 case AudioSystem.DEVICE_OUT_HDMI_ARC:
902 connType = AudioRoutesInfo.MAIN_HDMI;
903 break;
904 case AudioSystem.DEVICE_OUT_USB_DEVICE:
905 case AudioSystem.DEVICE_OUT_USB_HEADSET:
906 connType = AudioRoutesInfo.MAIN_USB;
907 break;
908 }
909
910 synchronized (mCurAudioRoutes) {
911 if (connType == 0) {
912 return;
913 }
914 int newConn = mCurAudioRoutes.mainType;
915 if (state != 0) {
916 newConn |= connType;
917 } else {
918 newConn &= ~connType;
919 }
920 if (newConn != mCurAudioRoutes.mainType) {
921 mCurAudioRoutes.mainType = newConn;
922 mDeviceBroker.postReportNewRoutes();
923 }
924 }
925 }
926
927 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
928 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
929 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
930 if (state != AudioService.CONNECTION_STATE_CONNECTED) {
931 return;
932 }
933 ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
934 int[] portGeneration = new int[1];
935 int status = AudioSystem.listAudioPorts(ports, portGeneration);
936 if (status != AudioManager.SUCCESS) {
937 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
938 return;
939 }
940 for (AudioPort port : ports) {
941 if (!(port instanceof AudioDevicePort)) {
942 continue;
943 }
944 final AudioDevicePort devicePort = (AudioDevicePort) port;
945 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
946 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
947 continue;
948 }
949 // found an HDMI port: format the list of supported encodings
950 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
951 if (formats.length > 0) {
952 ArrayList<Integer> encodingList = new ArrayList(1);
953 for (int format : formats) {
954 // a format in the list can be 0, skip it
955 if (format != AudioFormat.ENCODING_INVALID) {
956 encodingList.add(format);
957 }
958 }
959 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
960 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
961 }
962 // find the maximum supported number of channels
963 int maxChannels = 0;
964 for (int mask : devicePort.channelMasks()) {
965 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
966 if (channelCount > maxChannels) {
967 maxChannels = channelCount;
968 }
969 }
970 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
971 }
972 }
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700973
974 //----------------------------------------------------------
975 // For tests only
976
977 /**
978 * Check if device is in the list of connected devices
979 * @param device
980 * @return true if connected
981 */
982 @VisibleForTesting
983 public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
984 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
985 device.getAddress());
986 synchronized (mConnectedDevices) {
987 return (mConnectedDevices.get(key) != null);
988 }
989 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700990}