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