[DO NOT MERGE] AudioService: fix disconnect/connect of A2DP device
am: 8c92bde392
Change-Id: Idcc22099de1348d1a818be99131d63eb71377491
This commit is contained in:
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package com.android.server.audio;
|
||||
|
||||
import static com.android.server.audio.AudioService.CONNECTION_STATE_CONNECTED;
|
||||
import static com.android.server.audio.AudioService.CONNECTION_STATE_DISCONNECTED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.bluetooth.BluetoothA2dp;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
@@ -93,13 +90,28 @@ import com.android.internal.annotations.GuardedBy;
|
||||
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
|
||||
mContext = context;
|
||||
mAudioService = service;
|
||||
setupMessaging(context);
|
||||
mBtHelper = new BtHelper(this);
|
||||
mDeviceInventory = new AudioDeviceInventory(this);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/** for test purposes only, inject AudioDeviceInventory */
|
||||
AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
|
||||
@NonNull AudioDeviceInventory mockDeviceInventory) {
|
||||
mContext = context;
|
||||
mAudioService = service;
|
||||
mBtHelper = new BtHelper(this);
|
||||
mDeviceInventory = mockDeviceInventory;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setupMessaging(mContext);
|
||||
|
||||
mForcedUseForComm = AudioSystem.FORCE_NONE;
|
||||
mForcedUseForCommExt = mForcedUseForComm;
|
||||
|
||||
}
|
||||
|
||||
/*package*/ Context getContext() {
|
||||
@@ -230,17 +242,42 @@ import com.android.internal.annotations.GuardedBy;
|
||||
mSupprNoisy = suppressNoisyIntent;
|
||||
mVolume = vol;
|
||||
}
|
||||
|
||||
// redefine equality op so we can match messages intended for this device
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return mDevice.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
|
||||
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
|
||||
int profile, boolean suppressNoisyIntent, int a2dpVolume) {
|
||||
final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
|
||||
suppressNoisyIntent, a2dpVolume);
|
||||
|
||||
// TODO add a check to try to remove unprocessed messages for the same device (the old
|
||||
// check didn't work), and make sure it doesn't conflict with config change message
|
||||
sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
|
||||
// when receiving a request to change the connection state of a device, this last request
|
||||
// is the source of truth, so cancel all previous requests
|
||||
removeAllA2dpConnectionEvents(device);
|
||||
|
||||
sendLMsgNoDelay(
|
||||
state == BluetoothProfile.STATE_CONNECTED
|
||||
? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
|
||||
: MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
|
||||
SENDMSG_QUEUE, info);
|
||||
}
|
||||
|
||||
/** remove all previously scheduled connection and disconnection events for the given device */
|
||||
private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) {
|
||||
mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
|
||||
device);
|
||||
mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
|
||||
device);
|
||||
mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
|
||||
device);
|
||||
mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
|
||||
device);
|
||||
}
|
||||
|
||||
private static final class HearingAidDeviceConnectionInfo {
|
||||
@@ -428,13 +465,16 @@ import com.android.internal.annotations.GuardedBy;
|
||||
sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
|
||||
}
|
||||
|
||||
/*package*/ void postA2dpSinkConnection(int state,
|
||||
/*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state,
|
||||
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
|
||||
sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
|
||||
sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
|
||||
? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
|
||||
: MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
|
||||
SENDMSG_QUEUE,
|
||||
state, btDeviceInfo, delay);
|
||||
}
|
||||
|
||||
/*package*/ void postA2dpSourceConnection(int state,
|
||||
/*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state,
|
||||
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
|
||||
sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
|
||||
state, btDeviceInfo, delay);
|
||||
@@ -520,25 +560,6 @@ import com.android.internal.annotations.GuardedBy;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mDeviceStateLock")
|
||||
/*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
|
||||
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
|
||||
final int intState = (state == BluetoothA2dp.STATE_CONNECTED)
|
||||
? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;
|
||||
final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
|
||||
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
|
||||
AudioSystem.DEVICE_NONE);
|
||||
final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
|
||||
|
||||
if (AudioService.DEBUG_DEVICES) {
|
||||
Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
|
||||
+ " state= " + state
|
||||
+ " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
|
||||
}
|
||||
sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
|
||||
state, btDeviceInfo, delay);
|
||||
}
|
||||
|
||||
/*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
|
||||
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
|
||||
final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
|
||||
@@ -573,8 +594,10 @@ import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
// must be called synchronized on mConnectedDevices
|
||||
/*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
|
||||
return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
|
||||
new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
|
||||
return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
|
||||
new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
|
||||
|| mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
|
||||
new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
|
||||
}
|
||||
|
||||
/*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
|
||||
@@ -700,7 +723,8 @@ import com.android.internal.annotations.GuardedBy;
|
||||
mDeviceInventory.onReportNewRoutes();
|
||||
}
|
||||
break;
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
|
||||
synchronized (mDeviceStateLock) {
|
||||
mDeviceInventory.onSetA2dpSinkConnectionState(
|
||||
(BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
|
||||
@@ -825,7 +849,8 @@ import com.android.internal.annotations.GuardedBy;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: {
|
||||
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
|
||||
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: {
|
||||
final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj;
|
||||
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
|
||||
"setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent "
|
||||
@@ -876,7 +901,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
|
||||
private static final int MSG_IIL_SET_FORCE_USE = 4;
|
||||
private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
|
||||
private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
|
||||
private static final int MSG_TOGGLE_HDMI = 6;
|
||||
private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
|
||||
private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
|
||||
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
|
||||
@@ -887,7 +912,6 @@ import com.android.internal.annotations.GuardedBy;
|
||||
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
|
||||
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
|
||||
private static final int MSG_I_DISCONNECT_BT_SCO = 16;
|
||||
private static final int MSG_TOGGLE_HDMI = 17;
|
||||
private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
|
||||
private static final int MSG_DISCONNECT_A2DP = 19;
|
||||
private static final int MSG_DISCONNECT_A2DP_SINK = 20;
|
||||
@@ -897,25 +921,30 @@ import com.android.internal.annotations.GuardedBy;
|
||||
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK = 24;
|
||||
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID = 25;
|
||||
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET = 26;
|
||||
private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED = 27;
|
||||
private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED = 28;
|
||||
// process external command to (dis)connect an A2DP device
|
||||
private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27;
|
||||
private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION = 29;
|
||||
private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION = 30;
|
||||
// process external command to (dis)connect a hearing aid device
|
||||
private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28;
|
||||
private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
|
||||
// a ScoClient died in BtHelper
|
||||
private static final int MSG_L_SCOCLIENT_DIED = 29;
|
||||
private static final int MSG_L_SCOCLIENT_DIED = 32;
|
||||
|
||||
|
||||
private static boolean isMessageHandledUnderWakelock(int msgId) {
|
||||
switch(msgId) {
|
||||
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
|
||||
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
|
||||
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
|
||||
case MSG_IL_BTA2DP_DOCK_TIMEOUT:
|
||||
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
|
||||
case MSG_TOGGLE_HDMI:
|
||||
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
|
||||
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
|
||||
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
|
||||
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION:
|
||||
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
|
||||
return true;
|
||||
default:
|
||||
@@ -996,7 +1025,8 @@ import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
switch (msg) {
|
||||
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
|
||||
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
|
||||
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
|
||||
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
|
||||
case MSG_IL_BTA2DP_DOCK_TIMEOUT:
|
||||
|
||||
@@ -41,14 +41,16 @@ import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class to manage the inventory of all connected devices.
|
||||
* This class is thread-safe.
|
||||
* (non final for mocking/spying)
|
||||
*/
|
||||
public final class AudioDeviceInventory {
|
||||
public class AudioDeviceInventory {
|
||||
|
||||
private static final String TAG = "AS.AudioDeviceInventory";
|
||||
|
||||
@@ -56,11 +58,7 @@ public final class AudioDeviceInventory {
|
||||
// Key for map created from DeviceInfo.makeDeviceListKey()
|
||||
private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
|
||||
|
||||
private final @NonNull AudioDeviceBroker mDeviceBroker;
|
||||
|
||||
AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
|
||||
mDeviceBroker = broker;
|
||||
}
|
||||
private @NonNull AudioDeviceBroker mDeviceBroker;
|
||||
|
||||
// cache of the address of the last dock the device was connected to
|
||||
private String mDockAddress;
|
||||
@@ -70,6 +68,20 @@ public final class AudioDeviceInventory {
|
||||
final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
|
||||
new RemoteCallbackList<IAudioRoutesObserver>();
|
||||
|
||||
/*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
|
||||
mDeviceBroker = broker;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
/** for mocking only */
|
||||
/*package*/ AudioDeviceInventory() {
|
||||
mDeviceBroker = null;
|
||||
}
|
||||
|
||||
/*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
|
||||
mDeviceBroker = broker;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
/**
|
||||
* Class to store info about connected devices.
|
||||
@@ -146,8 +158,10 @@ public final class AudioDeviceInventory {
|
||||
}
|
||||
}
|
||||
|
||||
// only public for mocking/spying
|
||||
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
|
||||
/*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
|
||||
@VisibleForTesting
|
||||
public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
|
||||
@AudioService.BtProfileConnectionState int state) {
|
||||
final BluetoothDevice btDevice = btInfo.getBtDevice();
|
||||
int a2dpVolume = btInfo.getVolume();
|
||||
@@ -159,30 +173,40 @@ public final class AudioDeviceInventory {
|
||||
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||||
address = "";
|
||||
}
|
||||
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
|
||||
"A2DP sink connected: device addr=" + address + " state=" + state
|
||||
+ " vol=" + a2dpVolume));
|
||||
|
||||
final int a2dpCodec = btInfo.getCodec();
|
||||
|
||||
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
|
||||
"A2DP sink connected: device addr=" + address + " state=" + state
|
||||
+ " codec=" + a2dpCodec
|
||||
+ " vol=" + a2dpVolume));
|
||||
|
||||
synchronized (mConnectedDevices) {
|
||||
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
|
||||
btDevice.getAddress());
|
||||
final DeviceInfo di = mConnectedDevices.get(key);
|
||||
boolean isConnected = di != null;
|
||||
|
||||
if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
|
||||
if (btDevice.isBluetoothDock()) {
|
||||
if (state == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
// introduction of a delay for transient disconnections of docks when
|
||||
// power is rapidly turned off/on, this message will be canceled if
|
||||
// we reconnect the dock under a preset delay
|
||||
makeA2dpDeviceUnavailableLater(address,
|
||||
AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
|
||||
// the next time isConnected is evaluated, it will be false for the dock
|
||||
if (isConnected) {
|
||||
if (state == BluetoothProfile.STATE_CONNECTED) {
|
||||
// device is already connected, but we are receiving a connection again,
|
||||
// it could be for a codec change
|
||||
if (a2dpCodec != di.mDeviceCodecFormat) {
|
||||
mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
|
||||
}
|
||||
} else {
|
||||
makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
|
||||
if (btDevice.isBluetoothDock()) {
|
||||
if (state == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
// introduction of a delay for transient disconnections of docks when
|
||||
// power is rapidly turned off/on, this message will be canceled if
|
||||
// we reconnect the dock under a preset delay
|
||||
makeA2dpDeviceUnavailableLater(address,
|
||||
AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
|
||||
// the next time isConnected is evaluated, it will be false for the dock
|
||||
}
|
||||
} else {
|
||||
makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
|
||||
}
|
||||
}
|
||||
} else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
|
||||
if (btDevice.isBluetoothDock()) {
|
||||
@@ -282,11 +306,9 @@ public final class AudioDeviceInventory {
|
||||
+ " event=" + BtHelper.a2dpDeviceEventToString(event)));
|
||||
|
||||
synchronized (mConnectedDevices) {
|
||||
//TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
|
||||
// for this type of message
|
||||
if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
|
||||
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
|
||||
"A2dp config change ignored"));
|
||||
"A2dp config change ignored (scheduled connection change)"));
|
||||
return;
|
||||
}
|
||||
final String key = DeviceInfo.makeDeviceListKey(
|
||||
@@ -534,8 +556,10 @@ public final class AudioDeviceInventory {
|
||||
return mCurAudioRoutes;
|
||||
}
|
||||
|
||||
// only public for mocking/spying
|
||||
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
|
||||
/*package*/ void setBluetoothA2dpDeviceConnectionState(
|
||||
@VisibleForTesting
|
||||
public void setBluetoothA2dpDeviceConnectionState(
|
||||
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
|
||||
int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
|
||||
int delay;
|
||||
@@ -544,9 +568,12 @@ public final class AudioDeviceInventory {
|
||||
}
|
||||
synchronized (mConnectedDevices) {
|
||||
if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
|
||||
int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
|
||||
@AudioService.ConnectionState int asState =
|
||||
(state == BluetoothA2dp.STATE_CONNECTED)
|
||||
? AudioService.CONNECTION_STATE_CONNECTED
|
||||
: AudioService.CONNECTION_STATE_DISCONNECTED;
|
||||
delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
|
||||
intState, musicDevice);
|
||||
asState, musicDevice);
|
||||
} else {
|
||||
delay = 0;
|
||||
}
|
||||
@@ -785,7 +812,7 @@ public final class AudioDeviceInventory {
|
||||
return 0;
|
||||
}
|
||||
mDeviceBroker.postBroadcastBecomingNoisy();
|
||||
delay = 1000;
|
||||
delay = AudioService.BECOMING_NOISY_DELAY_MS;
|
||||
}
|
||||
|
||||
return delay;
|
||||
@@ -943,4 +970,21 @@ public final class AudioDeviceInventory {
|
||||
intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------
|
||||
// For tests only
|
||||
|
||||
/**
|
||||
* Check if device is in the list of connected devices
|
||||
* @param device
|
||||
* @return true if connected
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
|
||||
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
|
||||
device.getAddress());
|
||||
synchronized (mConnectedDevices) {
|
||||
return (mConnectedDevices.get(key) != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
@@ -200,6 +201,13 @@ public class AudioService extends IAudioService.Stub
|
||||
/** How long to delay after a volume down event before unmuting a stream */
|
||||
private static final int UNMUTE_STREAM_DELAY = 350;
|
||||
|
||||
/**
|
||||
* Delay before disconnecting a device that would cause BECOMING_NOISY intent to be sent,
|
||||
* to give a chance to applications to pause.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int BECOMING_NOISY_DELAY_MS = 1000;
|
||||
|
||||
/**
|
||||
* Only used in the result from {@link #checkForRingerModeChange(int, int, int)}
|
||||
*/
|
||||
@@ -4124,7 +4132,9 @@ public class AudioService extends IAudioService.Stub
|
||||
|| adjust == AudioManager.ADJUST_TOGGLE_MUTE;
|
||||
}
|
||||
|
||||
/*package*/ boolean isInCommunication() {
|
||||
/** only public for mocking/spying, do not call outside of AudioService */
|
||||
@VisibleForTesting
|
||||
public boolean isInCommunication() {
|
||||
boolean IsInCall = false;
|
||||
|
||||
TelecomManager telecomManager =
|
||||
@@ -4293,7 +4303,9 @@ public class AudioService extends IAudioService.Stub
|
||||
return false;
|
||||
}
|
||||
|
||||
/*package*/ int getDeviceForStream(int stream) {
|
||||
/** only public for mocking/spying, do not call outside of AudioService */
|
||||
@VisibleForTesting
|
||||
public int getDeviceForStream(int stream) {
|
||||
int device = getDevicesForStream(stream);
|
||||
if ((device & (device - 1)) != 0) {
|
||||
// Multiple device selection is either:
|
||||
@@ -4338,7 +4350,9 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void postObserveDevicesForAllStreams() {
|
||||
/** only public for mocking/spying, do not call outside of AudioService */
|
||||
@VisibleForTesting
|
||||
public void postObserveDevicesForAllStreams() {
|
||||
sendMsg(mAudioHandler,
|
||||
MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS,
|
||||
SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
|
||||
@@ -4449,7 +4463,9 @@ public class AudioService extends IAudioService.Stub
|
||||
AudioSystem.DEVICE_OUT_ALL_USB |
|
||||
AudioSystem.DEVICE_OUT_HDMI;
|
||||
|
||||
/*package*/ void postAccessoryPlugMediaUnmute(int newDevice) {
|
||||
/** only public for mocking/spying, do not call outside of AudioService */
|
||||
@VisibleForTesting
|
||||
public void postAccessoryPlugMediaUnmute(int newDevice) {
|
||||
sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
|
||||
newDevice, 0, null, 0);
|
||||
}
|
||||
@@ -4999,7 +5015,9 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device,
|
||||
/** only public for mocking/spying, do not call outside of AudioService */
|
||||
@VisibleForTesting
|
||||
public void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device,
|
||||
String caller) {
|
||||
sendMsg(mAudioHandler,
|
||||
MSG_SET_DEVICE_STREAM_VOLUME,
|
||||
@@ -5580,7 +5598,9 @@ public class AudioService extends IAudioService.Stub
|
||||
* @return true if there is currently a registered dynamic mixing policy that affects media
|
||||
* and is not a render + loopback policy
|
||||
*/
|
||||
/*package*/ boolean hasMediaDynamicPolicy() {
|
||||
// only public for mocking/spying
|
||||
@VisibleForTesting
|
||||
public boolean hasMediaDynamicPolicy() {
|
||||
synchronized (mAudioPolicies) {
|
||||
if (mAudioPolicies.isEmpty()) {
|
||||
return false;
|
||||
@@ -5913,7 +5933,9 @@ public class AudioService extends IAudioService.Stub
|
||||
return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
|
||||
}
|
||||
|
||||
/*package*/ boolean hasAudioFocusUsers() {
|
||||
/** only public for mocking/spying, do not call outside of AudioService */
|
||||
@VisibleForTesting
|
||||
public boolean hasAudioFocusUsers() {
|
||||
return mMediaFocusControl.hasAudioFocusUsers();
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,12 @@ public class BtHelper {
|
||||
public int getCodec() {
|
||||
return mCodec;
|
||||
}
|
||||
|
||||
// redefine equality op so we can match messages intended for this device
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return mBtDevice.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
// A2DP device events
|
||||
@@ -441,9 +447,9 @@ public class BtHelper {
|
||||
return;
|
||||
}
|
||||
final BluetoothDevice btDevice = deviceList.get(0);
|
||||
final @BluetoothProfile.BtProfileState int state = mA2dp.getConnectionState(btDevice);
|
||||
mDeviceBroker.handleSetA2dpSinkConnectionState(
|
||||
state, new BluetoothA2dpDeviceInfo(btDevice));
|
||||
// the device is guaranteed CONNECTED
|
||||
mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice,
|
||||
BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1);
|
||||
}
|
||||
|
||||
/*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) {
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
|
||||
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||
|
||||
|
||||
<!-- Uses API introduced in O (26) -->
|
||||
<uses-sdk android:minSdkVersion="1"
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.server.audio;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioSystem;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
|
||||
@MediumTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AudioDeviceBrokerTest {
|
||||
|
||||
private static final String TAG = "AudioDeviceBrokerTest";
|
||||
private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
|
||||
|
||||
private Context mContext;
|
||||
// the actual class under test
|
||||
private AudioDeviceBroker mAudioDeviceBroker;
|
||||
|
||||
@Mock private AudioService mMockAudioService;
|
||||
@Spy private AudioDeviceInventory mSpyDevInventory;
|
||||
|
||||
private BluetoothDevice mFakeBtDevice;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
mMockAudioService = mock(AudioService.class);
|
||||
mSpyDevInventory = spy(new AudioDeviceInventory());
|
||||
mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory);
|
||||
mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
|
||||
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
mFakeBtDevice = adapter.getRemoteDevice("00:01:02:03:04:05");
|
||||
Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception { }
|
||||
|
||||
@Test
|
||||
public void testSetUpAndTearDown() { }
|
||||
|
||||
/**
|
||||
* Verify call to postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection
|
||||
* calls into AudioDeviceInventory with the right params
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testPostA2dpDeviceConnectionChange() throws Exception {
|
||||
Log.i(TAG, "testPostA2dpDeviceConnectionChange");
|
||||
Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
|
||||
|
||||
mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
|
||||
Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
|
||||
verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState(
|
||||
any(BluetoothDevice.class),
|
||||
ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED) /*state*/,
|
||||
ArgumentMatchers.eq(BluetoothProfile.A2DP) /*profile*/,
|
||||
ArgumentMatchers.eq(true) /*suppressNoisyIntent*/, anyInt() /*musicDevice*/,
|
||||
ArgumentMatchers.eq(1) /*a2dpVolume*/
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify call to postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for
|
||||
* connection > pause > disconnection > connection
|
||||
* keeps the device connected
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testA2dpDeviceConnectionDisconnectionConnectionChange() throws Exception {
|
||||
Log.i(TAG, "testA2dpDeviceConnectionDisconnectionConnectionChange");
|
||||
|
||||
doTestConnectionDisconnectionReconnection(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify device disconnection and reconnection within the BECOMING_NOISY window
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testA2dpDeviceReconnectionWithinBecomingNoisyDelay() throws Exception {
|
||||
Log.i(TAG, "testA2dpDeviceReconnectionWithinBecomingNoisyDelay");
|
||||
|
||||
doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2);
|
||||
}
|
||||
|
||||
private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection)
|
||||
throws Exception {
|
||||
when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
|
||||
.thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
|
||||
when(mMockAudioService.isInCommunication()).thenReturn(false);
|
||||
when(mMockAudioService.hasMediaDynamicPolicy()).thenReturn(false);
|
||||
when(mMockAudioService.hasAudioFocusUsers()).thenReturn(false);
|
||||
|
||||
// first connection
|
||||
mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
|
||||
Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
|
||||
|
||||
// disconnection
|
||||
mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
|
||||
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1);
|
||||
if (delayAfterDisconnection > 0) {
|
||||
Thread.sleep(delayAfterDisconnection);
|
||||
}
|
||||
|
||||
// reconnection
|
||||
mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2);
|
||||
Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS);
|
||||
|
||||
// Verify disconnection has been cancelled and we're seeing two connections attempts,
|
||||
// with the device connected at the end of the test
|
||||
verify(mSpyDevInventory, times(2)).onSetA2dpSinkConnectionState(
|
||||
any(BtHelper.BluetoothA2dpDeviceInfo.class),
|
||||
ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED));
|
||||
Assert.assertTrue("Mock device not connected",
|
||||
mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user