Merge changes If2b3b414,I1039748d,Ib3cd38f5
* changes:
AudioService: fix disconnect/connect of A2DP device
audioservice: re send BT_SCO parameter to HAL when audioserver restarts
audioservice: fix hasMediaDynamicPolicy() for loopback and render policies
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 4d09c3b..6010b1dc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -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;
@@ -95,13 +92,28 @@
/*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() {
@@ -125,6 +137,8 @@
/*package*/ void onAudioServerDied() {
// Restore forced usage for communications and record
synchronized (mDeviceStateLock) {
+ AudioSystem.setParameters(
+ "BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
}
@@ -230,17 +244,42 @@
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 +467,16 @@
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 +562,6 @@
}
}
- @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 +596,10 @@
// 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) {
@@ -709,7 +734,8 @@
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);
@@ -834,7 +860,8 @@
}
}
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 "
@@ -885,7 +912,7 @@
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;
@@ -896,7 +923,6 @@
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;
@@ -906,25 +932,30 @@
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:
@@ -1005,7 +1036,8 @@
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:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index a9a8ef2..90973a8 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -41,14 +41,16 @@
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 @@
// 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 @@
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 @@
}
}
+ // 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 @@
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 @@
+ " 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 @@
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 @@
}
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 @@
return 0;
}
mDeviceBroker.postBroadcastBecomingNoisy();
- delay = 1000;
+ delay = AudioService.BECOMING_NOISY_DELAY_MS;
}
return delay;
@@ -943,4 +970,21 @@
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);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6c57be8..5bc2261 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -128,6 +128,7 @@
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.server.EventLogTags;
@@ -190,6 +191,13 @@
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)}
*/
private static final int FLAG_ADJUST_VOLUME = 1;
@@ -3950,7 +3958,9 @@
|| 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 =
@@ -4119,7 +4129,9 @@
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:
@@ -4164,7 +4176,9 @@
}
}
- /*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*/,
@@ -4275,7 +4289,9 @@
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);
}
@@ -4825,7 +4841,9 @@
}
}
- /*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,
@@ -5181,15 +5199,19 @@
/**
* @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;
}
final Collection<AudioPolicyProxy> appColl = mAudioPolicies.values();
for (AudioPolicyProxy app : appColl) {
- if (app.hasMixAffectingUsage(AudioAttributes.USAGE_MEDIA)) {
+ if (app.hasMixAffectingUsage(AudioAttributes.USAGE_MEDIA,
+ AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER)) {
return true;
}
}
@@ -5514,7 +5536,9 @@
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();
}
@@ -6961,9 +6985,10 @@
Binder.restoreCallingIdentity(identity);
}
- boolean hasMixAffectingUsage(int usage) {
+ boolean hasMixAffectingUsage(int usage, int excludedFlags) {
for (AudioMix mix : mMixes) {
- if (mix.isAffectingUsage(usage)) {
+ if (mix.isAffectingUsage(usage)
+ && ((mix.getRouteFlags() & excludedFlags) != excludedFlags)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 1a63f8f..9f1a6bd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -139,6 +139,12 @@
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 @@
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) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 25bd4ec..c1bbb30 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -69,7 +69,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<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"
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
new file mode 100644
index 0000000..5c2ad94
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -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));
+ }
+}