blob: a2b3574880c4e3b5a55044f4b72f5a33c40a91f7 [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;
jiabine1573102019-11-15 10:05:59 -080047import java.util.HashSet;
48import java.util.Set;
Jean-Michel Trivi58850372018-09-14 16:01:28 -070049
50/**
51 * Class to manage the inventory of all connected devices.
52 * This class is thread-safe.
Jean-Michel Trividc552e92019-06-24 10:39:19 -070053 * (non final for mocking/spying)
Jean-Michel Trivi58850372018-09-14 16:01:28 -070054 */
Jean-Michel Trividc552e92019-06-24 10:39:19 -070055public class AudioDeviceInventory {
Jean-Michel Trivi58850372018-09-14 16:01:28 -070056
57 private static final String TAG = "AS.AudioDeviceInventory";
58
59 // Actual list of connected devices
60 // Key for map created from DeviceInfo.makeDeviceListKey()
61 private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
62
Jean-Michel Trividc552e92019-06-24 10:39:19 -070063 private @NonNull AudioDeviceBroker mDeviceBroker;
Jean-Michel Trivi58850372018-09-14 16:01:28 -070064
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
Jean-Michel Trividc552e92019-06-24 10:39:19 -070073 /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
74 mDeviceBroker = broker;
75 }
76
77 //-----------------------------------------------------------
78 /** for mocking only */
79 /*package*/ AudioDeviceInventory() {
80 mDeviceBroker = null;
81 }
82
83 /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
84 mDeviceBroker = broker;
85 }
86
Jean-Michel Trivi58850372018-09-14 16:01:28 -070087 //------------------------------------------------------------
88 /**
89 * Class to store info about connected devices.
90 * Use makeDeviceListKey() to make a unique key for this list.
91 */
92 private static class DeviceInfo {
93 final int mDeviceType;
94 final String mDeviceName;
95 final String mDeviceAddress;
96 int mDeviceCodecFormat;
97
98 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
99 mDeviceType = deviceType;
100 mDeviceName = deviceName;
101 mDeviceAddress = deviceAddress;
102 mDeviceCodecFormat = deviceCodecFormat;
103 }
104
105 @Override
106 public String toString() {
107 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
108 + " name:" + mDeviceName
109 + " addr:" + mDeviceAddress
110 + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
111 }
112
113 /**
114 * Generate a unique key for the mConnectedDevices List by composing the device "type"
115 * and the "address" associated with a specific instance of that device type
116 */
117 private static String makeDeviceListKey(int device, String deviceAddress) {
118 return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
119 }
120 }
121
122 /**
123 * A class just for packaging up a set of connection parameters.
124 */
125 /*package*/ class WiredDeviceConnectionState {
126 public final int mType;
127 public final @AudioService.ConnectionState int mState;
128 public final String mAddress;
129 public final String mName;
130 public final String mCaller;
131
132 /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
133 String address, String name, String caller) {
134 mType = type;
135 mState = state;
136 mAddress = address;
137 mName = name;
138 mCaller = caller;
139 }
140 }
141
142 //------------------------------------------------------------
143 // Message handling from AudioDeviceBroker
144
145 /**
146 * Restore previously connected devices. Use in case of audio server crash
147 * (see AudioService.onAudioServerDied() method)
148 */
149 /*package*/ void onRestoreDevices() {
150 synchronized (mConnectedDevices) {
151 for (int i = 0; i < mConnectedDevices.size(); i++) {
152 DeviceInfo di = mConnectedDevices.valueAt(i);
153 AudioSystem.setDeviceConnectionState(
154 di.mDeviceType,
155 AudioSystem.DEVICE_STATE_AVAILABLE,
156 di.mDeviceAddress,
157 di.mDeviceName,
158 di.mDeviceCodecFormat);
159 }
160 }
161 }
162
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700163 // only public for mocking/spying
Jean-Michel Trivi2578b392019-10-29 16:34:42 +0000164 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700165 @VisibleForTesting
166 public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800167 @AudioService.BtProfileConnectionState int state) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700168 final BluetoothDevice btDevice = btInfo.getBtDevice();
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800169 int a2dpVolume = btInfo.getVolume();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700170 if (AudioService.DEBUG_DEVICES) {
171 Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
Jean-Michel Trivi89f142b2019-01-30 14:44:12 -0800172 + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700173 }
174 String address = btDevice.getAddress();
175 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
176 address = "";
177 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700178
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800179 final int a2dpCodec = btInfo.getCodec();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700180
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700181 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
182 "A2DP sink connected: device addr=" + address + " state=" + state
183 + " codec=" + a2dpCodec
184 + " vol=" + a2dpVolume));
185
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700186 synchronized (mConnectedDevices) {
187 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
188 btDevice.getAddress());
189 final DeviceInfo di = mConnectedDevices.get(key);
190 boolean isConnected = di != null;
191
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700192 if (isConnected) {
193 if (state == BluetoothProfile.STATE_CONNECTED) {
194 // device is already connected, but we are receiving a connection again,
195 // it could be for a codec change
196 if (a2dpCodec != di.mDeviceCodecFormat) {
197 mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700198 }
199 } else {
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700200 if (btDevice.isBluetoothDock()) {
201 if (state == BluetoothProfile.STATE_DISCONNECTED) {
202 // introduction of a delay for transient disconnections of docks when
203 // power is rapidly turned off/on, this message will be canceled if
204 // we reconnect the dock under a preset delay
205 makeA2dpDeviceUnavailableLater(address,
206 AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
207 // the next time isConnected is evaluated, it will be false for the dock
208 }
209 } else {
210 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
211 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700212 }
213 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
214 if (btDevice.isBluetoothDock()) {
215 // this could be a reconnection after a transient disconnection
216 mDeviceBroker.cancelA2dpDockTimeout();
217 mDockAddress = address;
218 } else {
219 // this could be a connection of another A2DP device before the timeout of
220 // a dock: cancel the dock timeout, and make the dock unavailable now
221 if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
222 mDeviceBroker.cancelA2dpDockTimeout();
223 makeA2dpDeviceUnavailableNow(mDockAddress,
224 AudioSystem.AUDIO_FORMAT_DEFAULT);
225 }
226 }
227 if (a2dpVolume != -1) {
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700228 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
229 // convert index to internal representation in VolumeStreamState
230 a2dpVolume * 10,
231 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700232 }
Jack Hecb499642019-04-03 00:48:49 -0700233 makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700234 "onSetA2dpSinkConnectionState", a2dpCodec);
235 }
236 }
237 }
238
239 /*package*/ void onSetA2dpSourceConnectionState(
240 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
241 final BluetoothDevice btDevice = btInfo.getBtDevice();
242 if (AudioService.DEBUG_DEVICES) {
243 Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
244 + state);
245 }
246 String address = btDevice.getAddress();
247 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
248 address = "";
249 }
250
251 synchronized (mConnectedDevices) {
252 final String key = DeviceInfo.makeDeviceListKey(
253 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
254 final DeviceInfo di = mConnectedDevices.get(key);
255 boolean isConnected = di != null;
256
257 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
258 makeA2dpSrcUnavailable(address);
259 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
260 makeA2dpSrcAvailable(address);
261 }
262 }
263 }
264
265 /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
Eric Laurent0cf98c72019-04-26 14:41:32 -0700266 @AudioService.BtProfileConnectionState int state, int streamType) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700267 String address = btDevice.getAddress();
268 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
269 address = "";
270 }
271 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
272 "onSetHearingAidConnectionState addr=" + address));
273
274 synchronized (mConnectedDevices) {
275 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
276 btDevice.getAddress());
277 final DeviceInfo di = mConnectedDevices.get(key);
278 boolean isConnected = di != null;
279
280 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
281 makeHearingAidDeviceUnavailable(address);
282 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
Eric Laurent0cf98c72019-04-26 14:41:32 -0700283 makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700284 "onSetHearingAidConnectionState");
285 }
286 }
287 }
288
Jean-Michel Trivi2578b392019-10-29 16:34:42 +0000289 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800290 /*package*/ void onBluetoothA2dpActiveDeviceChange(
291 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700292 final BluetoothDevice btDevice = btInfo.getBtDevice();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700293 if (btDevice == null) {
294 return;
295 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700296 if (AudioService.DEBUG_DEVICES) {
297 Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
298 }
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800299 int a2dpVolume = btInfo.getVolume();
300 final int a2dpCodec = btInfo.getCodec();
301
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700302 String address = btDevice.getAddress();
303 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
304 address = "";
305 }
306 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800307 "onBluetoothA2dpActiveDeviceChange addr=" + address
308 + " event=" + BtHelper.a2dpDeviceEventToString(event)));
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700309
310 synchronized (mConnectedDevices) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700311 if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
312 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700313 "A2dp config change ignored (scheduled connection change)"));
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700314 return;
315 }
316 final String key = DeviceInfo.makeDeviceListKey(
317 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
318 final DeviceInfo di = mConnectedDevices.get(key);
319 if (di == null) {
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800320 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange");
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700321 return;
322 }
323
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800324 if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
325 // Device is connected
326 if (a2dpVolume != -1) {
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700327 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
328 // convert index to internal representation in VolumeStreamState
329 a2dpVolume * 10,
330 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800331 "onBluetoothA2dpActiveDeviceChange");
Aniket Kumar Latad7c95982019-02-26 16:23:05 -0800332 }
333 } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
334 if (di.mDeviceCodecFormat != a2dpCodec) {
335 di.mDeviceCodecFormat = a2dpCodec;
336 mConnectedDevices.replace(key, di);
337 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700338 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700339 if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
Jack Hecb499642019-04-03 00:48:49 -0700340 BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700341 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
342 // force A2DP device disconnection in case of error so that AudioService state is
343 // consistent with audio policy manager state
344 setBluetoothA2dpDeviceConnectionState(
345 btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
346 false /* suppressNoisyIntent */, musicDevice,
347 -1 /* a2dpVolume */);
348 }
349 }
350 }
351
352 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
353 synchronized (mConnectedDevices) {
354 makeA2dpDeviceUnavailableNow(address, a2dpCodec);
355 }
356 }
357
358 /*package*/ void onReportNewRoutes() {
359 int n = mRoutesObservers.beginBroadcast();
360 if (n > 0) {
361 AudioRoutesInfo routes;
362 synchronized (mCurAudioRoutes) {
363 routes = new AudioRoutesInfo(mCurAudioRoutes);
364 }
365 while (n > 0) {
366 n--;
367 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
368 try {
369 obs.dispatchAudioRoutesChanged(routes);
370 } catch (RemoteException e) { }
371 }
372 }
373 mRoutesObservers.finishBroadcast();
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700374 mDeviceBroker.postObserveDevicesForAllStreams();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700375 }
376
jiabine1573102019-11-15 10:05:59 -0800377 private static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET;
378 static {
379 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>();
380 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
381 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
382 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE);
383 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
384 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700385
386 /*package*/ void onSetWiredDeviceConnectionState(
387 AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
388 AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
389
390 synchronized (mConnectedDevices) {
391 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
jiabine1573102019-11-15 10:05:59 -0800392 && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700393 mDeviceBroker.setBluetoothA2dpOnInt(true,
394 "onSetWiredDeviceConnectionState state DISCONNECTED");
395 }
396
Jean-Michel Trivi9c7d2b02019-04-25 11:47:15 -0700397 if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
398 wdcs.mType, wdcs.mAddress, wdcs.mName)) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700399 // change of connection state failed, bailout
400 return;
401 }
402 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
jiabine1573102019-11-15 10:05:59 -0800403 if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700404 mDeviceBroker.setBluetoothA2dpOnInt(false,
405 "onSetWiredDeviceConnectionState state not DISCONNECTED");
406 }
407 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
408 }
Jean-Michel Trivi9c7d2b02019-04-25 11:47:15 -0700409 if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
410 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
411 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700412 sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
413 updateAudioRoutes(wdcs.mType, wdcs.mState);
414 }
415 }
416
417 /*package*/ void onToggleHdmi() {
418 synchronized (mConnectedDevices) {
419 // Is HDMI connected?
420 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
421 final DeviceInfo di = mConnectedDevices.get(key);
422 if (di == null) {
423 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
424 return;
425 }
426 // Toggle HDMI to retrigger broadcast with proper formats.
427 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
428 AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
429 "android"); // disconnect
430 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
431 AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
432 "android"); // reconnect
433 }
434 }
435 //------------------------------------------------------------
436 //
437
438 /**
439 * Implements the communication with AudioSystem to (dis)connect a device in the native layers
440 * @param connect true if connection
441 * @param device the device type
442 * @param address the address of the device
443 * @param deviceName human-readable name of device
444 * @return false if an error was reported by AudioSystem
445 */
446 /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
447 String deviceName) {
448 if (AudioService.DEBUG_DEVICES) {
449 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
450 + Integer.toHexString(device) + " address:" + address
451 + " name:" + deviceName + ")");
452 }
453 synchronized (mConnectedDevices) {
454 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
455 if (AudioService.DEBUG_DEVICES) {
456 Slog.i(TAG, "deviceKey:" + deviceKey);
457 }
458 DeviceInfo di = mConnectedDevices.get(deviceKey);
459 boolean isConnected = di != null;
460 if (AudioService.DEBUG_DEVICES) {
461 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
462 }
463 if (connect && !isConnected) {
464 final int res = AudioSystem.setDeviceConnectionState(device,
465 AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
466 AudioSystem.AUDIO_FORMAT_DEFAULT);
467 if (res != AudioSystem.AUDIO_STATUS_OK) {
468 Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
469 + " due to command error " + res);
470 return false;
471 }
472 mConnectedDevices.put(deviceKey, new DeviceInfo(
473 device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
474 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
475 return true;
476 } else if (!connect && isConnected) {
477 AudioSystem.setDeviceConnectionState(device,
478 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
479 AudioSystem.AUDIO_FORMAT_DEFAULT);
480 // always remove even if disconnection failed
481 mConnectedDevices.remove(deviceKey);
482 return true;
483 }
484 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
485 + ", deviceSpec=" + di + ", connect=" + connect);
486 }
487 return false;
488 }
489
490
491 /*package*/ void disconnectA2dp() {
492 synchronized (mConnectedDevices) {
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800493 final ArraySet<String> toRemove = new ArraySet<>();
494 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
495 mConnectedDevices.values().forEach(deviceInfo -> {
496 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
497 toRemove.add(deviceInfo.mDeviceAddress);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700498 }
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800499 });
500 if (toRemove.size() > 0) {
501 final int delay = checkSendBecomingNoisyIntentInt(
502 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
503 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
504 toRemove.stream().forEach(deviceAddress ->
505 makeA2dpDeviceUnavailableLater(deviceAddress, delay)
506 );
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700507 }
508 }
509 }
510
511 /*package*/ void disconnectA2dpSink() {
512 synchronized (mConnectedDevices) {
513 final ArraySet<String> toRemove = new ArraySet<>();
514 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
515 mConnectedDevices.values().forEach(deviceInfo -> {
516 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
517 toRemove.add(deviceInfo.mDeviceAddress);
518 }
519 });
520 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
521 }
522 }
523
524 /*package*/ void disconnectHearingAid() {
525 synchronized (mConnectedDevices) {
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800526 final ArraySet<String> toRemove = new ArraySet<>();
527 // Disconnect ALL DEVICE_OUT_HEARING_AID devices
528 mConnectedDevices.values().forEach(deviceInfo -> {
529 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
530 toRemove.add(deviceInfo.mDeviceAddress);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700531 }
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800532 });
533 if (toRemove.size() > 0) {
534 final int delay = checkSendBecomingNoisyIntentInt(
535 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
536 toRemove.stream().forEach(deviceAddress ->
537 // TODO delay not used?
538 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
539 );
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700540 }
541 }
542 }
543
544 // must be called before removing the device from mConnectedDevices
545 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
546 // from AudioSystem
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800547 /*package*/ int checkSendBecomingNoisyIntent(int device,
548 @AudioService.ConnectionState int state, int musicDevice) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700549 synchronized (mConnectedDevices) {
550 return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
551 }
552 }
553
554 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
555 synchronized (mCurAudioRoutes) {
556 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
557 mRoutesObservers.register(observer);
558 return routes;
559 }
560 }
561
562 /*package*/ AudioRoutesInfo getCurAudioRoutes() {
563 return mCurAudioRoutes;
564 }
565
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700566 // only public for mocking/spying
Jean-Michel Trivi2578b392019-10-29 16:34:42 +0000567 @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700568 @VisibleForTesting
569 public void setBluetoothA2dpDeviceConnectionState(
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700570 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
571 int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
572 int delay;
573 if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
574 throw new IllegalArgumentException("invalid profile " + profile);
575 }
576 synchronized (mConnectedDevices) {
577 if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700578 @AudioService.ConnectionState int asState =
579 (state == BluetoothA2dp.STATE_CONNECTED)
580 ? AudioService.CONNECTION_STATE_CONNECTED
581 : AudioService.CONNECTION_STATE_DISCONNECTED;
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700582 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700583 asState, musicDevice);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700584 } else {
585 delay = 0;
586 }
587
588 final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
589
590 if (AudioService.DEBUG_DEVICES) {
591 Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
592 + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
593 + " suppressNoisyIntent: " + suppressNoisyIntent);
594 }
595
596 final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
597 new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
598 if (profile == BluetoothProfile.A2DP) {
Jean-Michel Trivif7345252019-05-29 14:25:02 -0700599 if (delay == 0) {
600 onSetA2dpSinkConnectionState(a2dpDeviceInfo, state);
601 } else {
602 mDeviceBroker.postA2dpSinkConnection(state,
603 a2dpDeviceInfo,
604 delay);
605 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700606 } else { //profile == BluetoothProfile.A2DP_SINK
607 mDeviceBroker.postA2dpSourceConnection(state,
608 a2dpDeviceInfo,
609 delay);
610 }
611 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700612 }
613
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700614 /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
615 String address, String name, String caller) {
616 synchronized (mConnectedDevices) {
617 int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
618 mDeviceBroker.postSetWiredDeviceConnectionState(
619 new WiredDeviceConnectionState(type, state, address, name, caller),
620 delay);
621 return delay;
622 }
623 }
624
625 /*package*/ int setBluetoothHearingAidDeviceConnectionState(
626 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
627 boolean suppressNoisyIntent, int musicDevice) {
628 int delay;
629 synchronized (mConnectedDevices) {
630 if (!suppressNoisyIntent) {
631 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
632 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
633 intState, musicDevice);
634 } else {
635 delay = 0;
636 }
637 mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
638 return delay;
639 }
640 }
641
642
643 //-------------------------------------------------------------------
644 // Internal utilities
645
646 @GuardedBy("mConnectedDevices")
647 private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
648 int a2dpCodec) {
649 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
650 // audio policy manager
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700651 mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
652 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
653 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
654 // Reset A2DP suspend state each time a new sink is connected
655 AudioSystem.setParameters("A2dpSuspended=false");
656 mConnectedDevices.put(
657 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
658 new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
659 address, a2dpCodec));
660 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
661 setCurrentAudioRouteNameIfPossible(name);
662 }
663
664 @GuardedBy("mConnectedDevices")
665 private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
666 if (address == null) {
667 return;
668 }
669 mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
670 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
671 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
672 mConnectedDevices.remove(
673 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
674 // Remove A2DP routes as well
675 setCurrentAudioRouteNameIfPossible(null);
676 if (mDockAddress == address) {
677 mDockAddress = null;
678 }
679 }
680
681 @GuardedBy("mConnectedDevices")
682 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
683 // prevent any activity on the A2DP audio output to avoid unwanted
684 // reconnection of the sink.
685 AudioSystem.setParameters("A2dpSuspended=true");
686 // retrieve DeviceInfo before removing device
687 final String deviceKey =
688 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
689 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
690 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
691 AudioSystem.AUDIO_FORMAT_DEFAULT;
692 // the device will be made unavailable later, so consider it disconnected right away
693 mConnectedDevices.remove(deviceKey);
694 // send the delayed message to make the device unavailable later
695 mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
696 }
697
698
699 @GuardedBy("mConnectedDevices")
700 private void makeA2dpSrcAvailable(String address) {
701 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
702 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
703 AudioSystem.AUDIO_FORMAT_DEFAULT);
704 mConnectedDevices.put(
705 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
706 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
707 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
708 }
709
710 @GuardedBy("mConnectedDevices")
711 private void makeA2dpSrcUnavailable(String address) {
712 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
713 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
714 AudioSystem.AUDIO_FORMAT_DEFAULT);
715 mConnectedDevices.remove(
716 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
717 }
718
719 @GuardedBy("mConnectedDevices")
Eric Laurent0cf98c72019-04-26 14:41:32 -0700720 private void makeHearingAidDeviceAvailable(
721 String address, String name, int streamType, String eventSource) {
722 final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700723 AudioSystem.DEVICE_OUT_HEARING_AID);
Eric Laurent0cf98c72019-04-26 14:41:32 -0700724 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700725
726 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
727 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
728 AudioSystem.AUDIO_FORMAT_DEFAULT);
729 mConnectedDevices.put(
730 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
731 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
732 address, AudioSystem.AUDIO_FORMAT_DEFAULT));
733 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
Eric Laurent0cf98c72019-04-26 14:41:32 -0700734 mDeviceBroker.postApplyVolumeOnDevice(streamType,
Jean-Michel Trivi5bbcd442019-04-18 12:07:34 -0700735 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700736 setCurrentAudioRouteNameIfPossible(name);
737 }
738
739 @GuardedBy("mConnectedDevices")
740 private void makeHearingAidDeviceUnavailable(String address) {
741 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
742 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
743 AudioSystem.AUDIO_FORMAT_DEFAULT);
744 mConnectedDevices.remove(
745 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
746 // Remove Hearing Aid routes as well
747 setCurrentAudioRouteNameIfPossible(null);
748 }
749
750 @GuardedBy("mConnectedDevices")
751 private void setCurrentAudioRouteNameIfPossible(String name) {
752 synchronized (mCurAudioRoutes) {
753 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
754 return;
755 }
756 if (name != null || !isCurrentDeviceConnected()) {
757 mCurAudioRoutes.bluetoothName = name;
758 mDeviceBroker.postReportNewRoutes();
759 }
760 }
761 }
762
763 @GuardedBy("mConnectedDevices")
764 private boolean isCurrentDeviceConnected() {
765 return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
766 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
767 }
768
769 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
770 // sent if:
771 // - none of these devices are connected anymore after one is disconnected AND
772 // - the device being disconnected is actually used for music.
773 // Access synchronized on mConnectedDevices
jiabine1573102019-11-15 10:05:59 -0800774 private static final Set<Integer> BECOMING_NOISY_INTENT_DEVICES_SET;
775 static {
776 BECOMING_NOISY_INTENT_DEVICES_SET = new HashSet<>();
777 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
778 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
779 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI);
780 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
781 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
782 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
783 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
784 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
785 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
786 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700787
788 // must be called before removing the device from mConnectedDevices
789 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
790 // from AudioSystem
791 @GuardedBy("mConnectedDevices")
Jean-Michel Trivic4b9c962019-02-01 09:58:54 -0800792 private int checkSendBecomingNoisyIntentInt(int device,
793 @AudioService.ConnectionState int state, int musicDevice) {
794 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700795 return 0;
796 }
jiabine1573102019-11-15 10:05:59 -0800797 if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) {
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700798 return 0;
799 }
800 int delay = 0;
jiabine1573102019-11-15 10:05:59 -0800801 Set<Integer> devices = new HashSet<>();
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700802 for (int i = 0; i < mConnectedDevices.size(); i++) {
803 int dev = mConnectedDevices.valueAt(i).mDeviceType;
804 if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
jiabine1573102019-11-15 10:05:59 -0800805 && BECOMING_NOISY_INTENT_DEVICES_SET.contains(dev)) {
806 devices.add(dev);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700807 }
808 }
809 if (musicDevice == AudioSystem.DEVICE_NONE) {
810 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
811 }
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800812
813 // always ignore condition on device being actually used for music when in communication
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700814 // because music routing is altered in this case.
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800815 // also checks whether media routing if affected by a dynamic policy or mirroring
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700816 if (((device == musicDevice) || mDeviceBroker.isInCommunication())
jiabine1573102019-11-15 10:05:59 -0800817 && AudioSystem.isSingleAudioDeviceType(devices, device)
818 && !mDeviceBroker.hasMediaDynamicPolicy()
819 && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) {
Jean-Michel Trivic1adecd2019-04-13 17:11:50 -0700820 if (!AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
821 && !mDeviceBroker.hasAudioFocusUsers()) {
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800822 // no media playback, not a "becoming noisy" situation, otherwise it could cause
823 // the pausing of some apps that are playing remotely
824 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
Jean-Michel Trivic1adecd2019-04-13 17:11:50 -0700825 "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
Jean-Michel Trivi3fbbf6e2019-03-05 14:55:37 -0800826 return 0;
827 }
Jean-Michel Trivib9465152019-02-06 15:20:19 -0800828 mDeviceBroker.postBroadcastBecomingNoisy();
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700829 delay = AudioService.BECOMING_NOISY_DELAY_MS;
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700830 }
831
832 return delay;
833 }
834
835 // Intent "extra" data keys.
836 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
837 private static final String CONNECT_INTENT_KEY_STATE = "state";
838 private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
839 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
840 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
841 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
842 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
843
844 private void sendDeviceConnectionIntent(int device, int state, String address,
845 String deviceName) {
846 if (AudioService.DEBUG_DEVICES) {
847 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
848 + " state:0x" + Integer.toHexString(state) + " address:" + address
849 + " name:" + deviceName + ");");
850 }
851 Intent intent = new Intent();
852
853 switch(device) {
854 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
855 intent.setAction(Intent.ACTION_HEADSET_PLUG);
856 intent.putExtra("microphone", 1);
857 break;
858 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
859 case AudioSystem.DEVICE_OUT_LINE:
860 intent.setAction(Intent.ACTION_HEADSET_PLUG);
861 intent.putExtra("microphone", 0);
862 break;
863 case AudioSystem.DEVICE_OUT_USB_HEADSET:
864 intent.setAction(Intent.ACTION_HEADSET_PLUG);
865 intent.putExtra("microphone",
866 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
867 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
868 break;
869 case AudioSystem.DEVICE_IN_USB_HEADSET:
870 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
871 == AudioSystem.DEVICE_STATE_AVAILABLE) {
872 intent.setAction(Intent.ACTION_HEADSET_PLUG);
873 intent.putExtra("microphone", 1);
874 } else {
875 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
876 return;
877 }
878 break;
879 case AudioSystem.DEVICE_OUT_HDMI:
880 case AudioSystem.DEVICE_OUT_HDMI_ARC:
881 configureHdmiPlugIntent(intent, state);
882 break;
883 }
884
885 if (intent.getAction() == null) {
886 return;
887 }
888
889 intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
890 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
891 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
892
893 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
894
895 final long ident = Binder.clearCallingIdentity();
896 try {
Paul McLeanb72445e2019-03-18 14:36:32 -0600897 ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT);
Jean-Michel Trivi58850372018-09-14 16:01:28 -0700898 } finally {
899 Binder.restoreCallingIdentity(ident);
900 }
901 }
902
903 private void updateAudioRoutes(int device, int state) {
904 int connType = 0;
905
906 switch (device) {
907 case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
908 connType = AudioRoutesInfo.MAIN_HEADSET;
909 break;
910 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
911 case AudioSystem.DEVICE_OUT_LINE:
912 connType = AudioRoutesInfo.MAIN_HEADPHONES;
913 break;
914 case AudioSystem.DEVICE_OUT_HDMI:
915 case AudioSystem.DEVICE_OUT_HDMI_ARC:
916 connType = AudioRoutesInfo.MAIN_HDMI;
917 break;
918 case AudioSystem.DEVICE_OUT_USB_DEVICE:
919 case AudioSystem.DEVICE_OUT_USB_HEADSET:
920 connType = AudioRoutesInfo.MAIN_USB;
921 break;
922 }
923
924 synchronized (mCurAudioRoutes) {
925 if (connType == 0) {
926 return;
927 }
928 int newConn = mCurAudioRoutes.mainType;
929 if (state != 0) {
930 newConn |= connType;
931 } else {
932 newConn &= ~connType;
933 }
934 if (newConn != mCurAudioRoutes.mainType) {
935 mCurAudioRoutes.mainType = newConn;
936 mDeviceBroker.postReportNewRoutes();
937 }
938 }
939 }
940
941 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
942 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
943 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
944 if (state != AudioService.CONNECTION_STATE_CONNECTED) {
945 return;
946 }
947 ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
948 int[] portGeneration = new int[1];
949 int status = AudioSystem.listAudioPorts(ports, portGeneration);
950 if (status != AudioManager.SUCCESS) {
951 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
952 return;
953 }
954 for (AudioPort port : ports) {
955 if (!(port instanceof AudioDevicePort)) {
956 continue;
957 }
958 final AudioDevicePort devicePort = (AudioDevicePort) port;
959 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
960 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
961 continue;
962 }
963 // found an HDMI port: format the list of supported encodings
964 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
965 if (formats.length > 0) {
966 ArrayList<Integer> encodingList = new ArrayList(1);
967 for (int format : formats) {
968 // a format in the list can be 0, skip it
969 if (format != AudioFormat.ENCODING_INVALID) {
970 encodingList.add(format);
971 }
972 }
973 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
974 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
975 }
976 // find the maximum supported number of channels
977 int maxChannels = 0;
978 for (int mask : devicePort.channelMasks()) {
979 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
980 if (channelCount > maxChannels) {
981 maxChannels = channelCount;
982 }
983 }
984 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
985 }
986 }
Jean-Michel Trividc552e92019-06-24 10:39:19 -0700987
988 //----------------------------------------------------------
989 // For tests only
990
991 /**
992 * Check if device is in the list of connected devices
993 * @param device
994 * @return true if connected
995 */
996 @VisibleForTesting
997 public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
998 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
999 device.getAddress());
1000 synchronized (mConnectedDevices) {
1001 return (mConnectedDevices.get(key) != null);
1002 }
1003 }
Jean-Michel Trivi58850372018-09-14 16:01:28 -07001004}