AudioService: mute music during BT A2DP device switch

Mute music stream when processing Bluetooth A2DP
device connection or disconnection messages instead of
relying on Bluetooth A2DP service to do it.
This fixes issues due to the async nature of device
disconnection and connection handling.

Bug: 144784716
Test: repro steps in bug.
Change-Id: I83b66cf35b39c29e82f9a6199cd6d31346258038
This commit is contained in:
Eric Laurent
2020-05-21 18:01:52 -07:00
parent 54b0b93ff7
commit 4599ee23cc
3 changed files with 117 additions and 4 deletions

View File

@@ -45,7 +45,10 @@ import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/** @hide */
@@ -59,6 +62,9 @@ import java.util.NoSuchElementException;
// Timeout for connection to bluetooth headset service
/*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
// Delay before checking it music should be unmuted after processing an A2DP message
private static final int BTA2DP_MUTE_CHECK_DELAY_MS = 50;
private final @NonNull AudioService mAudioService;
private final @NonNull Context mContext;
@@ -1050,9 +1056,19 @@ import java.util.NoSuchElementException;
final int strategy = msg.arg1;
mDeviceInventory.onSaveRemovePreferredDevice(strategy);
} break;
case MSG_CHECK_MUTE_MUSIC:
checkMessagesMuteMusic();
break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
// Give some time to Bluetooth service to post a connection message
// in case of active device switch
if (MESSAGES_MUTE_MUSIC.contains(msg.what)) {
sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS);
}
if (isMessageHandledUnderWakelock(msg.what)) {
try {
mBrokerEventWakeLock.release();
@@ -1116,6 +1132,7 @@ import java.util.NoSuchElementException;
private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;
private static final int MSG_CHECK_MUTE_MUSIC = 36;
private static boolean isMessageHandledUnderWakelock(int msgId) {
@@ -1132,6 +1149,7 @@ import java.util.NoSuchElementException;
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:
case MSG_CHECK_MUTE_MUSIC:
return true;
default:
return false;
@@ -1231,6 +1249,37 @@ import java.util.NoSuchElementException;
mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
time);
}
if (MESSAGES_MUTE_MUSIC.contains(msg)) {
checkMessagesMuteMusic();
}
}
/** List of messages for which music is muted while processing is pending */
private static final Set<Integer> MESSAGES_MUTE_MUSIC;
static {
MESSAGES_MUTE_MUSIC = new HashSet<>();
MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED);
MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION);
}
private AtomicBoolean mMusicMuted = new AtomicBoolean(false);
/** Mutes or unmutes music according to pending A2DP messages */
private void checkMessagesMuteMusic() {
boolean mute = false;
for (int msg : MESSAGES_MUTE_MUSIC) {
if (mBrokerHandler.hasMessages(msg)) {
mute = true;
break;
}
}
if (mute != mMusicMuted.getAndSet(mute)) {
mAudioService.setMusicMute(mute);
}
}
private class SpeakerphoneClient implements IBinder.DeathRecipient {

View File

@@ -5061,6 +5061,10 @@ public class AudioService extends IAudioService.Stub
profile, suppressNoisyIntent, a2dpVolume);
}
/*package*/ void setMusicMute(boolean mute) {
mStreamStates[AudioSystem.STREAM_MUSIC].muteInternally(mute);
}
/**
* See AudioManager.handleBluetoothA2dpDeviceConfigChange()
* @param device
@@ -5463,6 +5467,7 @@ public class AudioService extends IAudioService.Stub
private int mIndexMax;
private boolean mIsMuted;
private boolean mIsMutedInternally;
private String mVolumeIndexSettingName;
private int mObservedDevices;
@@ -5636,7 +5641,8 @@ public class AudioService extends IAudioService.Stub
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
// index is just set to 0 to repect BT requirements
if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0 && !mIsMuted) {
if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0
&& !isFullyMuted()) {
index = 1;
}
AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
@@ -5645,7 +5651,7 @@ public class AudioService extends IAudioService.Stub
// must be called while synchronized VolumeStreamState.class
/*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
int index;
if (mIsMuted) {
if (isFullyMuted()) {
index = 0;
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& isAvrcpAbsVolSupported) {
@@ -5668,7 +5674,7 @@ public class AudioService extends IAudioService.Stub
for (int i = 0; i < mIndexMap.size(); i++) {
final int device = mIndexMap.keyAt(i);
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
if (mIsMuted) {
if (isFullyMuted()) {
index = 0;
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& isAvrcpAbsVolSupported) {
@@ -5685,7 +5691,7 @@ public class AudioService extends IAudioService.Stub
}
// apply default volume last: by convention , default device volume will be used
// by audio policy manager if no explicit volume is present for a given device type
if (mIsMuted) {
if (isFullyMuted()) {
index = 0;
} else {
index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
@@ -5867,6 +5873,41 @@ public class AudioService extends IAudioService.Stub
return changed;
}
/**
* Mute/unmute the stream by AudioService
* @param state the new mute state
* @return true if the mute state was changed
*/
public boolean muteInternally(boolean state) {
boolean changed = false;
synchronized (VolumeStreamState.class) {
if (state != mIsMutedInternally) {
changed = true;
mIsMutedInternally = state;
// Set the new mute volume. This propagates the values to
// the audio system, otherwise the volume won't be changed
// at the lower level.
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
SENDMSG_QUEUE,
0,
0,
this, 0);
}
}
if (changed) {
sVolumeLogger.log(new VolumeEvent(
VolumeEvent.VOL_MUTE_STREAM_INT, mStreamType, state));
}
return changed;
}
@GuardedBy("VolumeStreamState.class")
public boolean isFullyMuted() {
return mIsMuted || mIsMutedInternally;
}
public int getStreamType() {
return mStreamType;
}
@@ -5903,6 +5944,8 @@ public class AudioService extends IAudioService.Stub
private void dump(PrintWriter pw) {
pw.print(" Muted: ");
pw.println(mIsMuted);
pw.print(" Muted Internally: ");
pw.println(mIsMutedInternally);
pw.print(" Min: ");
pw.print((mIndexMin + 5) / 10);
if (mIndexMin != mIndexMinNoPerm) {

View File

@@ -100,6 +100,7 @@ public class AudioServiceEvents {
static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
static final int VOL_MODE_CHANGE_HEARING_AID = 7;
static final int VOL_SET_GROUP_VOL = 8;
static final int VOL_MUTE_STREAM_INT = 9;
final int mOp;
final int mStream;
@@ -188,6 +189,18 @@ public class AudioServiceEvents {
logMetricEvent();
}
/** used for VOL_MUTE_STREAM_INT */
VolumeEvent(int op, int stream, boolean state) {
mOp = op;
mStream = stream;
mVal1 = state ? 1 : 0;
mVal2 = 0;
mCaller = null;
mGroupName = null;
mAudioAttributes = null;
logMetricEvent();
}
/**
* Audio Analytics unique Id.
@@ -278,6 +291,9 @@ public class AudioServiceEvents {
.set(MediaMetrics.Property.INDEX, mVal1)
.record();
return;
case VOL_MUTE_STREAM_INT:
// No value in logging metrics for this internal event
return;
default:
return;
}
@@ -343,6 +359,11 @@ public class AudioServiceEvents {
.append(" flags:0x").append(Integer.toHexString(mVal2))
.append(") from ").append(mCaller)
.toString();
case VOL_MUTE_STREAM_INT:
return new StringBuilder("VolumeStreamState.muteInternally(stream:")
.append(AudioSystem.streamToString(mStream))
.append(mVal1 == 1 ? ", muted)" : ", unmuted)")
.toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}