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