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.annotation.Nullable; 20 import android.app.ActivityManager; 21 import android.bluetooth.BluetoothA2dp; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothHearingAid; 25 import android.bluetooth.BluetoothProfile; 26 import android.content.Intent; 27 import android.media.AudioDeviceAttributes; 28 import android.media.AudioDevicePort; 29 import android.media.AudioFormat; 30 import android.media.AudioManager; 31 import android.media.AudioPort; 32 import android.media.AudioRoutesInfo; 33 import android.media.AudioSystem; 34 import android.media.IAudioRoutesObserver; 35 import android.media.IStrategyPreferredDeviceDispatcher; 36 import android.media.MediaMetrics; 37 import android.os.Binder; 38 import android.os.RemoteCallbackList; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.text.TextUtils; 42 import android.util.ArrayMap; 43 import android.util.ArraySet; 44 import android.util.Log; 45 import android.util.Slog; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.io.PrintWriter; 51 import java.util.ArrayList; 52 import java.util.HashSet; 53 import java.util.LinkedHashMap; 54 import java.util.Set; 55 56 /** 57 * Class to manage the inventory of all connected devices. 58 * This class is thread-safe. 59 * (non final for mocking/spying) 60 */ 61 public class AudioDeviceInventory { 62 63 private static final String TAG = "AS.AudioDeviceInventory"; 64 65 // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices 66 private final Object mDevicesLock = new Object(); 67 68 //Audio Analytics ids. 69 private static final String mMetricsId = "audio.device."; 70 71 // List of connected devices 72 // Key for map created from DeviceInfo.makeDeviceListKey() 73 @GuardedBy("mDevicesLock") 74 private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>() { 75 @Override 76 public DeviceInfo put(String key, DeviceInfo value) { 77 final DeviceInfo result = super.put(key, value); 78 record("put", true /* connected */, key, value); 79 return result; 80 } 81 82 @Override 83 public DeviceInfo putIfAbsent(String key, DeviceInfo value) { 84 final DeviceInfo result = super.putIfAbsent(key, value); 85 if (result == null) { 86 record("putIfAbsent", true /* connected */, key, value); 87 } 88 return result; 89 } 90 91 @Override 92 public DeviceInfo remove(Object key) { 93 final DeviceInfo result = super.remove(key); 94 if (result != null) { 95 record("remove", false /* connected */, (String) key, result); 96 } 97 return result; 98 } 99 100 @Override 101 public boolean remove(Object key, Object value) { 102 final boolean result = super.remove(key, value); 103 if (result) { 104 record("remove", false /* connected */, (String) key, (DeviceInfo) value); 105 } 106 return result; 107 } 108 109 // Not overridden 110 // clear 111 // compute 112 // computeIfAbsent 113 // computeIfPresent 114 // merge 115 // putAll 116 // replace 117 // replaceAll 118 private void record(String event, boolean connected, String key, DeviceInfo value) { 119 // DeviceInfo - int mDeviceType; 120 // DeviceInfo - int mDeviceCodecFormat; 121 new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE 122 + MediaMetrics.SEPARATOR + AudioSystem.getDeviceName(value.mDeviceType)) 123 .set(MediaMetrics.Property.ADDRESS, value.mDeviceAddress) 124 .set(MediaMetrics.Property.EVENT, event) 125 .set(MediaMetrics.Property.NAME, value.mDeviceName) 126 .set(MediaMetrics.Property.STATE, connected 127 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 128 .record(); 129 } 130 }; 131 132 // List of devices actually connected to AudioPolicy (through AudioSystem), only one 133 // by device type, which is used as the key, value is the DeviceInfo generated key. 134 // For the moment only for A2DP sink devices. 135 // TODO: extend to all device types 136 @GuardedBy("mDevicesLock") 137 private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>(); 138 139 // List of preferred devices for strategies 140 private final ArrayMap<Integer, AudioDeviceAttributes> mPreferredDevices = new ArrayMap<>(); 141 142 // the wrapper for AudioSystem static methods, allows us to spy AudioSystem 143 private final @NonNull AudioSystemAdapter mAudioSystem; 144 145 private @NonNull AudioDeviceBroker mDeviceBroker; 146 147 // Monitoring of audio routes. Protected by mAudioRoutes. 148 final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); 149 final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = 150 new RemoteCallbackList<IAudioRoutesObserver>(); 151 152 // Monitoring of strategy-preferred device 153 final RemoteCallbackList<IStrategyPreferredDeviceDispatcher> mPrefDevDispatchers = 154 new RemoteCallbackList<IStrategyPreferredDeviceDispatcher>(); 155 AudioDeviceInventory(@onNull AudioDeviceBroker broker)156 /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { 157 mDeviceBroker = broker; 158 mAudioSystem = AudioSystemAdapter.getDefaultAdapter(); 159 } 160 161 //----------------------------------------------------------- 162 /** for mocking only, allows to inject AudioSystem adapter */ AudioDeviceInventory(@onNull AudioSystemAdapter audioSystem)163 /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) { 164 mDeviceBroker = null; 165 mAudioSystem = audioSystem; 166 } 167 setDeviceBroker(@onNull AudioDeviceBroker broker)168 /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { 169 mDeviceBroker = broker; 170 } 171 172 //------------------------------------------------------------ 173 /** 174 * Class to store info about connected devices. 175 * Use makeDeviceListKey() to make a unique key for this list. 176 */ 177 private static class DeviceInfo { 178 final int mDeviceType; 179 final @NonNull String mDeviceName; 180 final @NonNull String mDeviceAddress; 181 int mDeviceCodecFormat; 182 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat)183 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { 184 mDeviceType = deviceType; 185 mDeviceName = deviceName == null ? "" : deviceName; 186 mDeviceAddress = deviceAddress == null ? "" : deviceAddress; 187 mDeviceCodecFormat = deviceCodecFormat; 188 } 189 190 @Override toString()191 public String toString() { 192 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) 193 + " (" + AudioSystem.getDeviceName(mDeviceType) 194 + ") name:" + mDeviceName 195 + " addr:" + mDeviceAddress 196 + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; 197 } 198 getKey()199 @NonNull String getKey() { 200 return makeDeviceListKey(mDeviceType, mDeviceAddress); 201 } 202 203 /** 204 * Generate a unique key for the mConnectedDevices List by composing the device "type" 205 * and the "address" associated with a specific instance of that device type 206 */ makeDeviceListKey(int device, String deviceAddress)207 @NonNull private static String makeDeviceListKey(int device, String deviceAddress) { 208 return "0x" + Integer.toHexString(device) + ":" + deviceAddress; 209 } 210 } 211 212 /** 213 * A class just for packaging up a set of connection parameters. 214 */ 215 /*package*/ class WiredDeviceConnectionState { 216 public final int mType; 217 public final @AudioService.ConnectionState int mState; 218 public final String mAddress; 219 public final String mName; 220 public final String mCaller; 221 WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller)222 /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, 223 String address, String name, String caller) { 224 mType = type; 225 mState = state; 226 mAddress = address; 227 mName = name; 228 mCaller = caller; 229 } 230 } 231 232 //------------------------------------------------------------ dump(PrintWriter pw, String prefix)233 /*package*/ void dump(PrintWriter pw, String prefix) { 234 pw.println("\n" + prefix + "Preferred devices for strategy:"); 235 mPreferredDevices.forEach((strategy, device) -> { 236 pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); 237 pw.println("\n" + prefix + "Connected devices:"); 238 mConnectedDevices.forEach((key, deviceInfo) -> { 239 pw.println(" " + prefix + deviceInfo.toString()); }); 240 pw.println("\n" + prefix + "APM Connected device (A2DP sink only):"); 241 mApmConnectedDevices.forEach((keyType, valueAddress) -> { 242 pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) 243 + " (" + AudioSystem.getDeviceName(keyType) 244 + ") addr:" + valueAddress); }); 245 } 246 247 //------------------------------------------------------------ 248 // Message handling from AudioDeviceBroker 249 250 /** 251 * Restore previously connected devices. Use in case of audio server crash 252 * (see AudioService.onAudioServerDied() method) 253 */ 254 // Always executed on AudioDeviceBroker message queue onRestoreDevices()255 /*package*/ void onRestoreDevices() { 256 synchronized (mDevicesLock) { 257 //TODO iterate on mApmConnectedDevices instead once it handles all device types 258 for (DeviceInfo di : mConnectedDevices.values()) { 259 mAudioSystem.setDeviceConnectionState( 260 di.mDeviceType, 261 AudioSystem.DEVICE_STATE_AVAILABLE, 262 di.mDeviceAddress, 263 di.mDeviceName, 264 di.mDeviceCodecFormat); 265 } 266 } 267 synchronized (mPreferredDevices) { 268 mPreferredDevices.forEach((strategy, device) -> { 269 mAudioSystem.setPreferredDeviceForStrategy(strategy, device); }); 270 } 271 } 272 273 // only public for mocking/spying 274 @GuardedBy("AudioDeviceBroker.mDeviceStateLock") 275 @VisibleForTesting onSetA2dpSinkConnectionState(@onNull BtHelper.BluetoothA2dpDeviceInfo btInfo, @AudioService.BtProfileConnectionState int state)276 public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, 277 @AudioService.BtProfileConnectionState int state) { 278 final BluetoothDevice btDevice = btInfo.getBtDevice(); 279 int a2dpVolume = btInfo.getVolume(); 280 if (AudioService.DEBUG_DEVICES) { 281 Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state=" 282 + state + " vol=" + a2dpVolume); 283 } 284 String address = btDevice.getAddress(); 285 if (address == null) { 286 address = ""; 287 } 288 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 289 address = ""; 290 } 291 292 final @AudioSystem.AudioFormatNativeEnumForBtCodec int a2dpCodec = btInfo.getCodec(); 293 294 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 295 "A2DP sink connected: device addr=" + address + " state=" + state 296 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec) 297 + " vol=" + a2dpVolume)); 298 299 new MediaMetrics.Item(mMetricsId + "a2dp") 300 .set(MediaMetrics.Property.ADDRESS, address) 301 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec)) 302 .set(MediaMetrics.Property.EVENT, "onSetA2dpSinkConnectionState") 303 .set(MediaMetrics.Property.INDEX, a2dpVolume) 304 .set(MediaMetrics.Property.STATE, 305 state == BluetoothProfile.STATE_CONNECTED 306 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 307 .record(); 308 309 synchronized (mDevicesLock) { 310 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 311 btDevice.getAddress()); 312 final DeviceInfo di = mConnectedDevices.get(key); 313 boolean isConnected = di != null; 314 315 if (isConnected) { 316 if (state == BluetoothProfile.STATE_CONNECTED) { 317 // device is already connected, but we are receiving a connection again, 318 // it could be for a codec change 319 if (a2dpCodec != di.mDeviceCodecFormat) { 320 mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice); 321 } 322 } else { 323 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); 324 } 325 } else if (state == BluetoothProfile.STATE_CONNECTED) { 326 // device is not already connected 327 if (a2dpVolume != -1) { 328 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, 329 // convert index to internal representation in VolumeStreamState 330 a2dpVolume * 10, 331 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState"); 332 } 333 makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice), 334 "onSetA2dpSinkConnectionState", a2dpCodec); 335 } 336 } 337 } 338 onSetA2dpSourceConnectionState( @onNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state)339 /*package*/ void onSetA2dpSourceConnectionState( 340 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) { 341 final BluetoothDevice btDevice = btInfo.getBtDevice(); 342 if (AudioService.DEBUG_DEVICES) { 343 Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" 344 + state); 345 } 346 String address = btDevice.getAddress(); 347 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 348 address = ""; 349 } 350 351 synchronized (mDevicesLock) { 352 final String key = DeviceInfo.makeDeviceListKey( 353 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); 354 final DeviceInfo di = mConnectedDevices.get(key); 355 boolean isConnected = di != null; 356 357 new MediaMetrics.Item(mMetricsId + "onSetA2dpSourceConnectionState") 358 .set(MediaMetrics.Property.ADDRESS, address) 359 .set(MediaMetrics.Property.DEVICE, 360 AudioSystem.getDeviceName(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP)) 361 .set(MediaMetrics.Property.STATE, 362 state == BluetoothProfile.STATE_CONNECTED 363 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 364 .record(); 365 366 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { 367 makeA2dpSrcUnavailable(address); 368 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { 369 makeA2dpSrcAvailable(address); 370 } 371 } 372 } 373 onSetHearingAidConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state, int streamType)374 /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice, 375 @AudioService.BtProfileConnectionState int state, int streamType) { 376 String address = btDevice.getAddress(); 377 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 378 address = ""; 379 } 380 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 381 "onSetHearingAidConnectionState addr=" + address)); 382 383 new MediaMetrics.Item(mMetricsId + "onSetHearingAidConnectionState") 384 .set(MediaMetrics.Property.ADDRESS, address) 385 .set(MediaMetrics.Property.DEVICE, 386 AudioSystem.getDeviceName(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP)) 387 .set(MediaMetrics.Property.STATE, 388 state == BluetoothProfile.STATE_CONNECTED 389 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 390 .set(MediaMetrics.Property.STREAM_TYPE, 391 AudioSystem.streamToString(streamType)) 392 .record(); 393 394 synchronized (mDevicesLock) { 395 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, 396 btDevice.getAddress()); 397 final DeviceInfo di = mConnectedDevices.get(key); 398 boolean isConnected = di != null; 399 400 if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { 401 makeHearingAidDeviceUnavailable(address); 402 } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { 403 makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType, 404 "onSetHearingAidConnectionState"); 405 } 406 } 407 } 408 409 @GuardedBy("AudioDeviceBroker.mDeviceStateLock") onBluetoothA2dpActiveDeviceChange( @onNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event)410 /*package*/ void onBluetoothA2dpActiveDeviceChange( 411 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { 412 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 413 + "onBluetoothA2dpActiveDeviceChange") 414 .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event)); 415 416 final BluetoothDevice btDevice = btInfo.getBtDevice(); 417 if (btDevice == null) { 418 mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record(); 419 return; 420 } 421 if (AudioService.DEBUG_DEVICES) { 422 Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); 423 } 424 int a2dpVolume = btInfo.getVolume(); 425 @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec(); 426 427 String address = btDevice.getAddress(); 428 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 429 address = ""; 430 } 431 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 432 "onBluetoothA2dpActiveDeviceChange addr=" + address 433 + " event=" + BtHelper.a2dpDeviceEventToString(event))); 434 435 synchronized (mDevicesLock) { 436 if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { 437 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 438 "A2dp config change ignored (scheduled connection change)") 439 .printLog(TAG)); 440 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored") 441 .record(); 442 return; 443 } 444 final String key = DeviceInfo.makeDeviceListKey( 445 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 446 final DeviceInfo di = mConnectedDevices.get(key); 447 if (di == null) { 448 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange"); 449 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record(); 450 return; 451 } 452 453 mmi.set(MediaMetrics.Property.ADDRESS, address) 454 .set(MediaMetrics.Property.ENCODING, 455 AudioSystem.audioFormatToString(a2dpCodec)) 456 .set(MediaMetrics.Property.INDEX, a2dpVolume) 457 .set(MediaMetrics.Property.NAME, di.mDeviceName); 458 459 if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) { 460 // Device is connected 461 if (a2dpVolume != -1) { 462 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, 463 // convert index to internal representation in VolumeStreamState 464 a2dpVolume * 10, 465 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 466 "onBluetoothA2dpActiveDeviceChange"); 467 } 468 } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { 469 if (di.mDeviceCodecFormat != a2dpCodec) { 470 di.mDeviceCodecFormat = a2dpCodec; 471 mConnectedDevices.replace(key, di); 472 } 473 } 474 final int res = mAudioSystem.handleDeviceConfigChange( 475 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, 476 BtHelper.getName(btDevice), a2dpCodec); 477 478 if (res != AudioSystem.AUDIO_STATUS_OK) { 479 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 480 "APM handleDeviceConfigChange failed for A2DP device addr=" + address 481 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) 482 .printLog(TAG)); 483 484 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); 485 // force A2DP device disconnection in case of error so that AudioService state is 486 // consistent with audio policy manager state 487 setBluetoothA2dpDeviceConnectionState( 488 btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, 489 false /* suppressNoisyIntent */, musicDevice, 490 -1 /* a2dpVolume */); 491 } else { 492 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 493 "APM handleDeviceConfigChange success for A2DP device addr=" + address 494 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) 495 .printLog(TAG)); 496 } 497 } 498 mmi.record(); 499 } 500 onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec)501 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { 502 synchronized (mDevicesLock) { 503 makeA2dpDeviceUnavailableNow(address, a2dpCodec); 504 } 505 } 506 onReportNewRoutes()507 /*package*/ void onReportNewRoutes() { 508 int n = mRoutesObservers.beginBroadcast(); 509 if (n > 0) { 510 new MediaMetrics.Item(mMetricsId + "onReportNewRoutes") 511 .set(MediaMetrics.Property.OBSERVERS, n) 512 .record(); 513 AudioRoutesInfo routes; 514 synchronized (mCurAudioRoutes) { 515 routes = new AudioRoutesInfo(mCurAudioRoutes); 516 } 517 while (n > 0) { 518 n--; 519 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n); 520 try { 521 obs.dispatchAudioRoutesChanged(routes); 522 } catch (RemoteException e) { } 523 } 524 } 525 mRoutesObservers.finishBroadcast(); 526 mDeviceBroker.postObserveDevicesForAllStreams(); 527 } 528 529 private static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET; 530 static { 531 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>(); 532 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET); 533 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); 534 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE); 535 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); 536 } 537 onSetWiredDeviceConnectionState( AudioDeviceInventory.WiredDeviceConnectionState wdcs)538 /*package*/ void onSetWiredDeviceConnectionState( 539 AudioDeviceInventory.WiredDeviceConnectionState wdcs) { 540 AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); 541 542 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 543 + "onSetWiredDeviceConnectionState") 544 .set(MediaMetrics.Property.ADDRESS, wdcs.mAddress) 545 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(wdcs.mType)) 546 .set(MediaMetrics.Property.STATE, 547 wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED 548 ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED); 549 synchronized (mDevicesLock) { 550 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) 551 && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) { 552 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, 553 "onSetWiredDeviceConnectionState state DISCONNECTED"); 554 } 555 556 if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, 557 wdcs.mType, wdcs.mAddress, wdcs.mName)) { 558 // change of connection state failed, bailout 559 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") 560 .record(); 561 return; 562 } 563 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { 564 if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) { 565 mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/, 566 "onSetWiredDeviceConnectionState state not DISCONNECTED"); 567 } 568 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller); 569 } 570 if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) { 571 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); 572 } 573 sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName); 574 updateAudioRoutes(wdcs.mType, wdcs.mState); 575 } 576 mmi.record(); 577 } 578 onToggleHdmi()579 /*package*/ void onToggleHdmi() { 580 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi") 581 .set(MediaMetrics.Property.DEVICE, 582 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI)); 583 synchronized (mDevicesLock) { 584 // Is HDMI connected? 585 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); 586 final DeviceInfo di = mConnectedDevices.get(key); 587 if (di == null) { 588 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi"); 589 mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record(); 590 return; 591 } 592 // Toggle HDMI to retrigger broadcast with proper formats. 593 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, 594 AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", 595 "android"); // disconnect 596 setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, 597 AudioSystem.DEVICE_STATE_AVAILABLE, "", "", 598 "android"); // reconnect 599 } 600 mmi.record(); 601 } 602 onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAttributes device)603 /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAttributes device) { 604 mPreferredDevices.put(strategy, device); 605 dispatchPreferredDevice(strategy, device); 606 } 607 onSaveRemovePreferredDevice(int strategy)608 /*package*/ void onSaveRemovePreferredDevice(int strategy) { 609 mPreferredDevices.remove(strategy); 610 dispatchPreferredDevice(strategy, null); 611 } 612 613 //------------------------------------------------------------ 614 // 615 setPreferredDeviceForStrategySync(int strategy, @NonNull AudioDeviceAttributes device)616 /*package*/ int setPreferredDeviceForStrategySync(int strategy, 617 @NonNull AudioDeviceAttributes device) { 618 final long identity = Binder.clearCallingIdentity(); 619 final int status = mAudioSystem.setPreferredDeviceForStrategy(strategy, device); 620 Binder.restoreCallingIdentity(identity); 621 622 if (status == AudioSystem.SUCCESS) { 623 mDeviceBroker.postSaveSetPreferredDeviceForStrategy(strategy, device); 624 } 625 return status; 626 } 627 removePreferredDeviceForStrategySync(int strategy)628 /*package*/ int removePreferredDeviceForStrategySync(int strategy) { 629 final long identity = Binder.clearCallingIdentity(); 630 final int status = mAudioSystem.removePreferredDeviceForStrategy(strategy); 631 Binder.restoreCallingIdentity(identity); 632 633 if (status == AudioSystem.SUCCESS) { 634 mDeviceBroker.postSaveRemovePreferredDeviceForStrategy(strategy); 635 } 636 return status; 637 } 638 registerStrategyPreferredDeviceDispatcher( @onNull IStrategyPreferredDeviceDispatcher dispatcher)639 /*package*/ void registerStrategyPreferredDeviceDispatcher( 640 @NonNull IStrategyPreferredDeviceDispatcher dispatcher) { 641 mPrefDevDispatchers.register(dispatcher); 642 } 643 unregisterStrategyPreferredDeviceDispatcher( @onNull IStrategyPreferredDeviceDispatcher dispatcher)644 /*package*/ void unregisterStrategyPreferredDeviceDispatcher( 645 @NonNull IStrategyPreferredDeviceDispatcher dispatcher) { 646 mPrefDevDispatchers.unregister(dispatcher); 647 } 648 649 /** 650 * Implements the communication with AudioSystem to (dis)connect a device in the native layers 651 * @param connect true if connection 652 * @param device the device type 653 * @param address the address of the device 654 * @param deviceName human-readable name of device 655 * @return false if an error was reported by AudioSystem 656 */ handleDeviceConnection(boolean connect, int device, String address, String deviceName)657 /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, 658 String deviceName) { 659 if (AudioService.DEBUG_DEVICES) { 660 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" 661 + Integer.toHexString(device) + " address:" + address 662 + " name:" + deviceName + ")"); 663 } 664 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection") 665 .set(MediaMetrics.Property.ADDRESS, address) 666 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device)) 667 .set(MediaMetrics.Property.MODE, connect 668 ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT) 669 .set(MediaMetrics.Property.NAME, deviceName); 670 synchronized (mDevicesLock) { 671 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); 672 if (AudioService.DEBUG_DEVICES) { 673 Slog.i(TAG, "deviceKey:" + deviceKey); 674 } 675 DeviceInfo di = mConnectedDevices.get(deviceKey); 676 boolean isConnected = di != null; 677 if (AudioService.DEBUG_DEVICES) { 678 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); 679 } 680 if (connect && !isConnected) { 681 final int res = mAudioSystem.setDeviceConnectionState(device, 682 AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, 683 AudioSystem.AUDIO_FORMAT_DEFAULT); 684 if (res != AudioSystem.AUDIO_STATUS_OK) { 685 final String reason = "not connecting device 0x" + Integer.toHexString(device) 686 + " due to command error " + res; 687 Slog.e(TAG, reason); 688 mmi.set(MediaMetrics.Property.EARLY_RETURN, reason) 689 .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED) 690 .record(); 691 return false; 692 } 693 mConnectedDevices.put(deviceKey, new DeviceInfo( 694 device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 695 mDeviceBroker.postAccessoryPlugMediaUnmute(device); 696 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); 697 return true; 698 } else if (!connect && isConnected) { 699 mAudioSystem.setDeviceConnectionState(device, 700 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, 701 AudioSystem.AUDIO_FORMAT_DEFAULT); 702 // always remove even if disconnection failed 703 mConnectedDevices.remove(deviceKey); 704 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); 705 return true; 706 } 707 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey 708 + ", deviceSpec=" + di + ", connect=" + connect); 709 } 710 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); 711 return false; 712 } 713 714 disconnectA2dp()715 /*package*/ void disconnectA2dp() { 716 synchronized (mDevicesLock) { 717 final ArraySet<String> toRemove = new ArraySet<>(); 718 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices 719 mConnectedDevices.values().forEach(deviceInfo -> { 720 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { 721 toRemove.add(deviceInfo.mDeviceAddress); 722 } 723 }); 724 new MediaMetrics.Item(mMetricsId + "disconnectA2dp") 725 .record(); 726 if (toRemove.size() > 0) { 727 final int delay = checkSendBecomingNoisyIntentInt( 728 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 729 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); 730 toRemove.stream().forEach(deviceAddress -> 731 makeA2dpDeviceUnavailableLater(deviceAddress, delay) 732 ); 733 } 734 } 735 } 736 disconnectA2dpSink()737 /*package*/ void disconnectA2dpSink() { 738 synchronized (mDevicesLock) { 739 final ArraySet<String> toRemove = new ArraySet<>(); 740 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices 741 mConnectedDevices.values().forEach(deviceInfo -> { 742 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { 743 toRemove.add(deviceInfo.mDeviceAddress); 744 } 745 }); 746 new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink") 747 .record(); 748 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); 749 } 750 } 751 disconnectHearingAid()752 /*package*/ void disconnectHearingAid() { 753 synchronized (mDevicesLock) { 754 final ArraySet<String> toRemove = new ArraySet<>(); 755 // Disconnect ALL DEVICE_OUT_HEARING_AID devices 756 mConnectedDevices.values().forEach(deviceInfo -> { 757 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { 758 toRemove.add(deviceInfo.mDeviceAddress); 759 } 760 }); 761 new MediaMetrics.Item(mMetricsId + "disconnectHearingAid") 762 .record(); 763 if (toRemove.size() > 0) { 764 final int delay = checkSendBecomingNoisyIntentInt( 765 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); 766 toRemove.stream().forEach(deviceAddress -> 767 // TODO delay not used? 768 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) 769 ); 770 } 771 } 772 } 773 774 // must be called before removing the device from mConnectedDevices 775 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying 776 // from AudioSystem checkSendBecomingNoisyIntent(int device, @AudioService.ConnectionState int state, int musicDevice)777 /*package*/ int checkSendBecomingNoisyIntent(int device, 778 @AudioService.ConnectionState int state, int musicDevice) { 779 synchronized (mDevicesLock) { 780 return checkSendBecomingNoisyIntentInt(device, state, musicDevice); 781 } 782 } 783 startWatchingRoutes(IAudioRoutesObserver observer)784 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { 785 synchronized (mCurAudioRoutes) { 786 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); 787 mRoutesObservers.register(observer); 788 return routes; 789 } 790 } 791 getCurAudioRoutes()792 /*package*/ AudioRoutesInfo getCurAudioRoutes() { 793 return mCurAudioRoutes; 794 } 795 796 // only public for mocking/spying 797 @GuardedBy("AudioDeviceBroker.mDeviceStateLock") 798 @VisibleForTesting setBluetoothA2dpDeviceConnectionState( @onNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume)799 public void setBluetoothA2dpDeviceConnectionState( 800 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, 801 int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) { 802 int delay; 803 if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { 804 throw new IllegalArgumentException("invalid profile " + profile); 805 } 806 synchronized (mDevicesLock) { 807 if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { 808 @AudioService.ConnectionState int asState = 809 (state == BluetoothA2dp.STATE_CONNECTED) 810 ? AudioService.CONNECTION_STATE_CONNECTED 811 : AudioService.CONNECTION_STATE_DISCONNECTED; 812 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 813 asState, musicDevice); 814 } else { 815 delay = 0; 816 } 817 818 final int a2dpCodec = mDeviceBroker.getA2dpCodec(device); 819 820 if (AudioService.DEBUG_DEVICES) { 821 Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device 822 + " state: " + state + " delay(ms): " + delay 823 + " codec:" + Integer.toHexString(a2dpCodec) 824 + " suppressNoisyIntent: " + suppressNoisyIntent); 825 } 826 827 final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo = 828 new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec); 829 if (profile == BluetoothProfile.A2DP) { 830 mDeviceBroker.postA2dpSinkConnection(state, 831 a2dpDeviceInfo, 832 delay); 833 } else { //profile == BluetoothProfile.A2DP_SINK 834 mDeviceBroker.postA2dpSourceConnection(state, 835 a2dpDeviceInfo, 836 delay); 837 } 838 } 839 } 840 setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller)841 /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, 842 String address, String name, String caller) { 843 synchronized (mDevicesLock) { 844 int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE); 845 mDeviceBroker.postSetWiredDeviceConnectionState( 846 new WiredDeviceConnectionState(type, state, address, name, caller), 847 delay); 848 return delay; 849 } 850 } 851 setBluetoothHearingAidDeviceConnectionState( @onNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent, int musicDevice)852 /*package*/ int setBluetoothHearingAidDeviceConnectionState( 853 @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, 854 boolean suppressNoisyIntent, int musicDevice) { 855 int delay; 856 synchronized (mDevicesLock) { 857 if (!suppressNoisyIntent) { 858 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; 859 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID, 860 intState, musicDevice); 861 } else { 862 delay = 0; 863 } 864 mDeviceBroker.postSetHearingAidConnectionState(state, device, delay); 865 if (state == BluetoothHearingAid.STATE_CONNECTED) { 866 mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE, 867 "HEARING_AID set to CONNECTED"); 868 } 869 return delay; 870 } 871 } 872 873 874 //------------------------------------------------------------------- 875 // Internal utilities 876 877 @GuardedBy("mDevicesLock") makeA2dpDeviceAvailable(String address, String name, String eventSource, int a2dpCodec)878 private void makeA2dpDeviceAvailable(String address, String name, String eventSource, 879 int a2dpCodec) { 880 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in 881 // audio policy manager 882 mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); 883 // at this point there could be another A2DP device already connected in APM, but it 884 // doesn't matter as this new one will overwrite the previous one 885 final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 886 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); 887 888 // TODO: log in MediaMetrics once distinction between connection failure and 889 // double connection is made. 890 if (res != AudioSystem.AUDIO_STATUS_OK) { 891 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 892 "APM failed to make available A2DP device addr=" + address 893 + " error=" + res).printLog(TAG)); 894 // TODO: connection failed, stop here 895 // TODO: return; 896 } else { 897 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 898 "A2DP device addr=" + address + " now available").printLog(TAG)); 899 } 900 901 // Reset A2DP suspend state each time a new sink is connected 902 mAudioSystem.setParameters("A2dpSuspended=false"); 903 904 final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, 905 address, a2dpCodec); 906 final String diKey = di.getKey(); 907 mConnectedDevices.put(diKey, di); 908 // on a connection always overwrite the device seen by AudioPolicy, see comment above when 909 // calling AudioSystem 910 mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey); 911 912 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); 913 setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); 914 } 915 916 @GuardedBy("mDevicesLock") makeA2dpDeviceUnavailableNow(String address, int a2dpCodec)917 private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { 918 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address) 919 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec)) 920 .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow"); 921 922 if (address == null) { 923 mmi.set(MediaMetrics.Property.EARLY_RETURN, "address null").record(); 924 return; 925 } 926 final String deviceToRemoveKey = 927 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 928 929 mConnectedDevices.remove(deviceToRemoveKey); 930 if (!deviceToRemoveKey 931 .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { 932 // removing A2DP device not currently used by AudioPolicy, log but don't act on it 933 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 934 "A2DP device " + address + " made unavailable, was not used")).printLog(TAG)); 935 mmi.set(MediaMetrics.Property.EARLY_RETURN, 936 "A2DP device made unavailable, was not used") 937 .record(); 938 return; 939 } 940 941 // device to remove was visible by APM, update APM 942 mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false); 943 final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 944 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); 945 946 if (res != AudioSystem.AUDIO_STATUS_OK) { 947 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 948 "APM failed to make unavailable A2DP device addr=" + address 949 + " error=" + res).printLog(TAG)); 950 // TODO: failed to disconnect, stop here 951 // TODO: return; 952 } else { 953 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 954 "A2DP device addr=" + address + " made unavailable")).printLog(TAG)); 955 } 956 mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); 957 // Remove A2DP routes as well 958 setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); 959 mmi.record(); 960 } 961 962 @GuardedBy("mDevicesLock") makeA2dpDeviceUnavailableLater(String address, int delayMs)963 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { 964 // prevent any activity on the A2DP audio output to avoid unwanted 965 // reconnection of the sink. 966 mAudioSystem.setParameters("A2dpSuspended=true"); 967 // retrieve DeviceInfo before removing device 968 final String deviceKey = 969 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 970 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); 971 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat : 972 AudioSystem.AUDIO_FORMAT_DEFAULT; 973 // the device will be made unavailable later, so consider it disconnected right away 974 mConnectedDevices.remove(deviceKey); 975 // send the delayed message to make the device unavailable later 976 mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs); 977 } 978 979 980 @GuardedBy("mDevicesLock") makeA2dpSrcAvailable(String address)981 private void makeA2dpSrcAvailable(String address) { 982 mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, 983 AudioSystem.DEVICE_STATE_AVAILABLE, address, "", 984 AudioSystem.AUDIO_FORMAT_DEFAULT); 985 mConnectedDevices.put( 986 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), 987 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", 988 address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 989 } 990 991 @GuardedBy("mDevicesLock") makeA2dpSrcUnavailable(String address)992 private void makeA2dpSrcUnavailable(String address) { 993 mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, 994 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", 995 AudioSystem.AUDIO_FORMAT_DEFAULT); 996 mConnectedDevices.remove( 997 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); 998 } 999 1000 @GuardedBy("mDevicesLock") makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource)1001 private void makeHearingAidDeviceAvailable( 1002 String address, String name, int streamType, String eventSource) { 1003 final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, 1004 AudioSystem.DEVICE_OUT_HEARING_AID); 1005 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); 1006 1007 mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, 1008 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, 1009 AudioSystem.AUDIO_FORMAT_DEFAULT); 1010 mConnectedDevices.put( 1011 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), 1012 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, 1013 address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 1014 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); 1015 mDeviceBroker.postApplyVolumeOnDevice(streamType, 1016 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); 1017 setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); 1018 new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") 1019 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") 1020 .set(MediaMetrics.Property.DEVICE, 1021 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) 1022 .set(MediaMetrics.Property.NAME, name) 1023 .set(MediaMetrics.Property.STREAM_TYPE, 1024 AudioSystem.streamToString(streamType)) 1025 .record(); 1026 } 1027 1028 @GuardedBy("mDevicesLock") makeHearingAidDeviceUnavailable(String address)1029 private void makeHearingAidDeviceUnavailable(String address) { 1030 mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, 1031 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", 1032 AudioSystem.AUDIO_FORMAT_DEFAULT); 1033 mConnectedDevices.remove( 1034 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); 1035 // Remove Hearing Aid routes as well 1036 setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); 1037 new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable") 1038 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") 1039 .set(MediaMetrics.Property.DEVICE, 1040 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) 1041 .record(); 1042 } 1043 1044 @GuardedBy("mDevicesLock") setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp)1045 private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { 1046 synchronized (mCurAudioRoutes) { 1047 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { 1048 return; 1049 } 1050 if (name != null || !isCurrentDeviceConnected()) { 1051 mCurAudioRoutes.bluetoothName = name; 1052 mDeviceBroker.postReportNewRoutes(fromA2dp); 1053 } 1054 } 1055 } 1056 1057 @GuardedBy("mDevicesLock") isCurrentDeviceConnected()1058 private boolean isCurrentDeviceConnected() { 1059 return mConnectedDevices.values().stream().anyMatch(deviceInfo -> 1060 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); 1061 } 1062 1063 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only 1064 // sent if: 1065 // - none of these devices are connected anymore after one is disconnected AND 1066 // - the device being disconnected is actually used for music. 1067 // Access synchronized on mConnectedDevices 1068 private static final Set<Integer> BECOMING_NOISY_INTENT_DEVICES_SET; 1069 static { 1070 BECOMING_NOISY_INTENT_DEVICES_SET = new HashSet<>(); 1071 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET); 1072 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); 1073 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI); 1074 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); 1075 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); 1076 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE); 1077 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID); 1078 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); 1079 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); 1080 } 1081 1082 // must be called before removing the device from mConnectedDevices 1083 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying 1084 // from AudioSystem 1085 @GuardedBy("mDevicesLock") checkSendBecomingNoisyIntentInt(int device, @AudioService.ConnectionState int state, int musicDevice)1086 private int checkSendBecomingNoisyIntentInt(int device, 1087 @AudioService.ConnectionState int state, int musicDevice) { 1088 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 1089 + "checkSendBecomingNoisyIntentInt") 1090 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device)) 1091 .set(MediaMetrics.Property.STATE, 1092 state == AudioService.CONNECTION_STATE_CONNECTED 1093 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED); 1094 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { 1095 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 1096 return 0; 1097 } 1098 if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) { 1099 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 1100 return 0; 1101 } 1102 int delay = 0; 1103 Set<Integer> devices = new HashSet<>(); 1104 for (DeviceInfo di : mConnectedDevices.values()) { 1105 if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0) 1106 && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) { 1107 devices.add(di.mDeviceType); 1108 } 1109 } 1110 if (musicDevice == AudioSystem.DEVICE_NONE) { 1111 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); 1112 } 1113 1114 // always ignore condition on device being actually used for music when in communication 1115 // because music routing is altered in this case. 1116 // also checks whether media routing if affected by a dynamic policy or mirroring 1117 if (((device == musicDevice) || mDeviceBroker.isInCommunication()) 1118 && AudioSystem.isSingleAudioDeviceType(devices, device) 1119 && !mDeviceBroker.hasMediaDynamicPolicy() 1120 && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) { 1121 if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/) 1122 && !mDeviceBroker.hasAudioFocusUsers()) { 1123 // no media playback, not a "becoming noisy" situation, otherwise it could cause 1124 // the pausing of some apps that are playing remotely 1125 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 1126 "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); 1127 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 1128 return 0; 1129 } 1130 mDeviceBroker.postBroadcastBecomingNoisy(); 1131 delay = AudioService.BECOMING_NOISY_DELAY_MS; 1132 } 1133 1134 mmi.set(MediaMetrics.Property.DELAY_MS, delay).record(); 1135 return delay; 1136 } 1137 1138 // Intent "extra" data keys. 1139 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; 1140 private static final String CONNECT_INTENT_KEY_STATE = "state"; 1141 private static final String CONNECT_INTENT_KEY_ADDRESS = "address"; 1142 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; 1143 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; 1144 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; 1145 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; 1146 sendDeviceConnectionIntent(int device, int state, String address, String deviceName)1147 private void sendDeviceConnectionIntent(int device, int state, String address, 1148 String deviceName) { 1149 if (AudioService.DEBUG_DEVICES) { 1150 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) 1151 + " state:0x" + Integer.toHexString(state) + " address:" + address 1152 + " name:" + deviceName + ");"); 1153 } 1154 Intent intent = new Intent(); 1155 1156 switch(device) { 1157 case AudioSystem.DEVICE_OUT_WIRED_HEADSET: 1158 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1159 intent.putExtra("microphone", 1); 1160 break; 1161 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: 1162 case AudioSystem.DEVICE_OUT_LINE: 1163 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1164 intent.putExtra("microphone", 0); 1165 break; 1166 case AudioSystem.DEVICE_OUT_USB_HEADSET: 1167 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1168 intent.putExtra("microphone", 1169 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") 1170 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); 1171 break; 1172 case AudioSystem.DEVICE_IN_USB_HEADSET: 1173 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") 1174 == AudioSystem.DEVICE_STATE_AVAILABLE) { 1175 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1176 intent.putExtra("microphone", 1); 1177 } else { 1178 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing 1179 return; 1180 } 1181 break; 1182 case AudioSystem.DEVICE_OUT_HDMI: 1183 case AudioSystem.DEVICE_OUT_HDMI_ARC: 1184 configureHdmiPlugIntent(intent, state); 1185 break; 1186 } 1187 1188 if (intent.getAction() == null) { 1189 return; 1190 } 1191 1192 intent.putExtra(CONNECT_INTENT_KEY_STATE, state); 1193 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); 1194 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); 1195 1196 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 1197 1198 final long ident = Binder.clearCallingIdentity(); 1199 try { 1200 ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT); 1201 } finally { 1202 Binder.restoreCallingIdentity(ident); 1203 } 1204 } 1205 updateAudioRoutes(int device, int state)1206 private void updateAudioRoutes(int device, int state) { 1207 int connType = 0; 1208 1209 switch (device) { 1210 case AudioSystem.DEVICE_OUT_WIRED_HEADSET: 1211 connType = AudioRoutesInfo.MAIN_HEADSET; 1212 break; 1213 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: 1214 case AudioSystem.DEVICE_OUT_LINE: 1215 connType = AudioRoutesInfo.MAIN_HEADPHONES; 1216 break; 1217 case AudioSystem.DEVICE_OUT_HDMI: 1218 case AudioSystem.DEVICE_OUT_HDMI_ARC: 1219 connType = AudioRoutesInfo.MAIN_HDMI; 1220 break; 1221 case AudioSystem.DEVICE_OUT_USB_DEVICE: 1222 case AudioSystem.DEVICE_OUT_USB_HEADSET: 1223 connType = AudioRoutesInfo.MAIN_USB; 1224 break; 1225 } 1226 1227 synchronized (mCurAudioRoutes) { 1228 if (connType == 0) { 1229 return; 1230 } 1231 int newConn = mCurAudioRoutes.mainType; 1232 if (state != 0) { 1233 newConn |= connType; 1234 } else { 1235 newConn &= ~connType; 1236 } 1237 if (newConn != mCurAudioRoutes.mainType) { 1238 mCurAudioRoutes.mainType = newConn; 1239 mDeviceBroker.postReportNewRoutes(false /*fromA2dp*/); 1240 } 1241 } 1242 } 1243 configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state)1244 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) { 1245 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); 1246 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); 1247 if (state != AudioService.CONNECTION_STATE_CONNECTED) { 1248 return; 1249 } 1250 ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); 1251 int[] portGeneration = new int[1]; 1252 int status = AudioSystem.listAudioPorts(ports, portGeneration); 1253 if (status != AudioManager.SUCCESS) { 1254 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent"); 1255 return; 1256 } 1257 for (AudioPort port : ports) { 1258 if (!(port instanceof AudioDevicePort)) { 1259 continue; 1260 } 1261 final AudioDevicePort devicePort = (AudioDevicePort) port; 1262 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI 1263 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) { 1264 continue; 1265 } 1266 // found an HDMI port: format the list of supported encodings 1267 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); 1268 if (formats.length > 0) { 1269 ArrayList<Integer> encodingList = new ArrayList(1); 1270 for (int format : formats) { 1271 // a format in the list can be 0, skip it 1272 if (format != AudioFormat.ENCODING_INVALID) { 1273 encodingList.add(format); 1274 } 1275 } 1276 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray(); 1277 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); 1278 } 1279 // find the maximum supported number of channels 1280 int maxChannels = 0; 1281 for (int mask : devicePort.channelMasks()) { 1282 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); 1283 if (channelCount > maxChannels) { 1284 maxChannels = channelCount; 1285 } 1286 } 1287 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); 1288 } 1289 } 1290 dispatchPreferredDevice(int strategy, @Nullable AudioDeviceAttributes device)1291 private void dispatchPreferredDevice(int strategy, @Nullable AudioDeviceAttributes device) { 1292 final int nbDispatchers = mPrefDevDispatchers.beginBroadcast(); 1293 for (int i = 0; i < nbDispatchers; i++) { 1294 try { 1295 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDeviceChanged(strategy, device); 1296 } catch (RemoteException e) { 1297 } 1298 } 1299 mPrefDevDispatchers.finishBroadcast(); 1300 } 1301 1302 //---------------------------------------------------------- 1303 // For tests only 1304 1305 /** 1306 * Check if device is in the list of connected devices 1307 * @param device 1308 * @return true if connected 1309 */ 1310 @VisibleForTesting isA2dpDeviceConnected(@onNull BluetoothDevice device)1311 public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { 1312 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 1313 device.getAddress()); 1314 synchronized (mDevicesLock) { 1315 return (mConnectedDevices.get(key) != null); 1316 } 1317 } 1318 } 1319