diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index df4c269d6afaa..67e27c41bbc78 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -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 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 { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 792de195887c1..10f3cf36f7049 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -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) { diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 591356746e38c..f3ff02f3aedc8 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -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(); } }