diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index deaa9313faddd..a3bae521a162d 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -15,6 +15,9 @@ */ 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; @@ -43,7 +46,7 @@ import java.util.ArrayList; /** @hide */ /*package*/ final class AudioDeviceBroker { - private static final String TAG = "AudioDeviceBroker"; + private static final String TAG = "AS.AudioDeviceBroker"; private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s @@ -62,27 +65,27 @@ import java.util.ArrayList; private int mForcedUseForCommExt; // Manages all connected devices, only ever accessed on the message loop - //### or make it synchronized private final AudioDeviceInventory mDeviceInventory; // Manages notifications to BT service private final BtHelper mBtHelper; //------------------------------------------------------------------- + // we use a different lock than mDeviceStateLock so as not to create + // lock contention between enqueueing a message and handling them private static final Object sLastDeviceConnectionMsgTimeLock = new Object(); + @GuardedBy("sLastDeviceConnectionMsgTimeLock") private static long sLastDeviceConnectMsgTime = 0; - private final Object mBluetoothA2dpEnabledLock = new Object(); + // General lock to be taken whenever the state of the audio devices is to be checked or changed + private final Object mDeviceStateLock = new Object(); + // Request to override default use of A2DP for media. - @GuardedBy("mBluetoothA2dpEnabledLock") + @GuardedBy("mDeviceStateLock") private boolean mBluetoothA2dpEnabled; - // lock always taken synchronized on mConnectedDevices - /*package*/ final Object mA2dpAvrcpLock = new Object(); - // lock always taken synchronized on mConnectedDevices - /*package*/ final Object mHearingAidLock = new Object(); - // lock always taken when accessing AudioService.mSetModeDeathHandlers + // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055 /*package*/ final Object mSetModeLock = new Object(); //------------------------------------------------------------------- @@ -109,13 +112,17 @@ import java.util.ArrayList; // All post* methods are asynchronous /*package*/ void onSystemReady() { - mBtHelper.onSystemReady(); + synchronized (mDeviceStateLock) { + mBtHelper.onSystemReady(); + } } /*package*/ void onAudioServerDied() { // Restore forced usage for communications and record - onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied"); - onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied"); + synchronized (mDeviceStateLock) { + onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied"); + onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied"); + } // restore devices sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE); } @@ -130,7 +137,9 @@ import java.util.ArrayList; } /*package*/ void disconnectAllBluetoothProfiles() { - mBtHelper.disconnectAllBluetoothProfiles(); + synchronized (mDeviceStateLock) { + mBtHelper.disconnectAllBluetoothProfiles(); + } } /** @@ -140,11 +149,13 @@ import java.util.ArrayList; * @param intent */ /*package*/ void receiveBtEvent(@NonNull Intent intent) { - mBtHelper.receiveBtEvent(intent); + synchronized (mDeviceStateLock) { + mBtHelper.receiveBtEvent(intent); + } } /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { - synchronized (mBluetoothA2dpEnabledLock) { + synchronized (mDeviceStateLock) { if (mBluetoothA2dpEnabled == on) { return; } @@ -158,28 +169,34 @@ import java.util.ArrayList; } /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) { - if (on) { - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + synchronized (mDeviceStateLock) { + if (on) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { + mForcedUseForComm = AudioSystem.FORCE_NONE; } - mForcedUseForComm = AudioSystem.FORCE_SPEAKER; - } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - mForcedUseForCommExt = mForcedUseForComm; - setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + mForcedUseForCommExt = mForcedUseForComm; + setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + } } /*package*/ boolean isSpeakerphoneOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + synchronized (mDeviceStateLock) { + return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + } } /*package*/ void setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller) { //TODO move logging here just like in setBluetooth* methods - mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller); + synchronized (mDeviceStateLock) { + mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller); + } } /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( @@ -192,22 +209,27 @@ import java.util.ArrayList; + " addr=" + device.getAddress() + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent + " vol=" + a2dpVolume)).printLog(TAG)); - if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, - new BtHelper.BluetoothA2dpDeviceInfo(device))) { - AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2DP connection state ignored")); - return 0; + synchronized (mDeviceStateLock) { + if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(device))) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP connection state ignored")); + return 0; + } + return mDeviceInventory.setBluetoothA2dpDeviceConnectionState( + device, state, profile, suppressNoisyIntent, + AudioSystem.DEVICE_NONE, a2dpVolume); } - return mDeviceInventory.setBluetoothA2dpDeviceConnectionState( - device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); } /*package*/ int handleBluetoothA2dpActiveDeviceChange( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { - return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile, - suppressNoisyIntent, a2dpVolume); + synchronized (mDeviceStateLock) { + return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); + } } /*package*/ int setBluetoothHearingAidDeviceConnectionState( @@ -218,57 +240,69 @@ import java.util.ArrayList; + " addr=" + device.getAddress() + " supprNoisy=" + suppressNoisyIntent + " src=" + eventSource)).printLog(TAG)); - return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState( - device, state, suppressNoisyIntent, musicDevice); + synchronized (mDeviceStateLock) { + return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice); + } } // never called by system components /*package*/ void setBluetoothScoOnByApp(boolean on) { - mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + synchronized (mDeviceStateLock) { + mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + } } /*package*/ boolean isBluetoothScoOnForApp() { - return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO; + synchronized (mDeviceStateLock) { + return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO; + } } /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); - if (on) { - // do not accept SCO ON if SCO audio is not connected - if (!mBtHelper.isBluetoothScoOn()) { - mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; - return; + synchronized (mDeviceStateLock) { + if (on) { + // do not accept SCO ON if SCO audio is not connected + if (!mBtHelper.isBluetoothScoOn()) { + mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; + return; + } + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + mForcedUseForComm = AudioSystem.FORCE_NONE; } - mForcedUseForComm = AudioSystem.FORCE_BT_SCO; - } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - mForcedUseForComm = AudioSystem.FORCE_NONE; + mForcedUseForCommExt = mForcedUseForComm; + AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off")); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource); + // Un-mute ringtone stream volume + mAudioService.setUpdateRingerModeServiceInt(); } - mForcedUseForCommExt = mForcedUseForComm; - AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off")); - sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); - sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource); - // Un-mute ringtone stream volume - mAudioService.setUpdateRingerModeServiceInt(); } /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { - return mDeviceInventory.startWatchingRoutes(observer); + synchronized (mDeviceStateLock) { + return mDeviceInventory.startWatchingRoutes(observer); + } } /*package*/ AudioRoutesInfo getCurAudioRoutes() { - return mDeviceInventory.getCurAudioRoutes(); + synchronized (mDeviceStateLock) { + return mDeviceInventory.getCurAudioRoutes(); + } } /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { - synchronized (mA2dpAvrcpLock) { + synchronized (mDeviceStateLock) { return mBtHelper.isAvrcpAbsoluteVolumeSupported(); } } /*package*/ boolean isBluetoothA2dpOn() { - synchronized (mBluetoothA2dpEnabledLock) { + synchronized (mDeviceStateLock) { return mBluetoothA2dpEnabled; } } @@ -355,14 +389,12 @@ import java.util.ArrayList; sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); } - //###TODO unify with handleSetA2dpSinkConnectionState /*package*/ void postA2dpSinkConnection(int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, state, btDeviceInfo, delay); } - //###TODO unify with handleSetA2dpSourceConnectionState /*package*/ void postA2dpSourceConnection(int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, @@ -395,7 +427,7 @@ import java.util.ArrayList; .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).append(" src:").append(source).toString(); - synchronized (mBluetoothA2dpEnabledLock) { + synchronized (mDeviceStateLock) { mBluetoothA2dpEnabled = on; mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); onSetForceUse( @@ -407,25 +439,38 @@ import java.util.ArrayList; /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, String deviceName) { - return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName); + synchronized (mDeviceStateLock) { + return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName); + } } /*package*/ void handleDisconnectA2dp() { - mDeviceInventory.disconnectA2dp(); + synchronized (mDeviceStateLock) { + mDeviceInventory.disconnectA2dp(); + } } /*package*/ void handleDisconnectA2dpSink() { - mDeviceInventory.disconnectA2dpSink(); + synchronized (mDeviceStateLock) { + mDeviceInventory.disconnectA2dpSink(); + } + } + + /*package*/ void handleDisconnectHearingAid() { + synchronized (mDeviceStateLock) { + mDeviceInventory.disconnectHearingAid(); + } } /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { - final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version? - // might be ok here because called on BT thread? + sync happening in - // checkSendBecomingNoisyIntent - final int delay = mDeviceInventory.checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, - AudioSystem.DEVICE_NONE); + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) + ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED; + final int delay; + synchronized (mDeviceStateLock) { + 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) { @@ -437,10 +482,6 @@ import java.util.ArrayList; state, btDeviceInfo, delay); } - /*package*/ void handleDisconnectHearingAid() { - mDeviceInventory.disconnectHearingAid(); - } - /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; @@ -468,8 +509,6 @@ import java.util.ArrayList; sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo); } - //### - // must be called synchronized on mConnectedDevices /*package*/ boolean hasScheduledA2dpDockTimeout() { return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); } @@ -486,19 +525,19 @@ import java.util.ArrayList; } /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { - synchronized (mA2dpAvrcpLock) { + synchronized (mDeviceStateLock) { mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); } } /*package*/ boolean getBluetoothA2dpEnabled() { - synchronized (mBluetoothA2dpEnabledLock) { + synchronized (mDeviceStateLock) { return mBluetoothA2dpEnabled; } } /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { - synchronized (mA2dpAvrcpLock) { + synchronized (mDeviceStateLock) { return mBtHelper.getA2dpCodec(device); } } @@ -579,71 +618,97 @@ import java.util.ArrayList; public void handleMessage(Message msg) { switch (msg.what) { case MSG_RESTORE_DEVICES: - mDeviceInventory.onRestoreDevices(); - synchronized (mBluetoothA2dpEnabledLock) { + synchronized (mDeviceStateLock) { + mDeviceInventory.onRestoreDevices(); mBtHelper.onAudioServerDiedRestoreA2dp(); } break; case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: - mDeviceInventory.onSetWiredDeviceConnectionState( - (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj); + synchronized (mDeviceStateLock) { + mDeviceInventory.onSetWiredDeviceConnectionState( + (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj); + } break; case MSG_I_BROADCAST_BT_CONNECTION_STATE: - mBtHelper.onBroadcastScoConnectionState(msg.arg1); + synchronized (mDeviceStateLock) { + mBtHelper.onBroadcastScoConnectionState(msg.arg1); + } break; - case MSG_IIL_SET_FORCE_USE: // intented fall-through + case MSG_IIL_SET_FORCE_USE: // intended fall-through case MSG_IIL_SET_FORCE_BT_A2DP_USE: onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj); break; case MSG_REPORT_NEW_ROUTES: - mDeviceInventory.onReportNewRoutes(); + synchronized (mDeviceStateLock) { + mDeviceInventory.onReportNewRoutes(); + } break; case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: - mDeviceInventory.onSetA2dpSinkConnectionState( - (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + synchronized (mDeviceStateLock) { + mDeviceInventory.onSetA2dpSinkConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + } break; case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: - mDeviceInventory.onSetA2dpSourceConnectionState( - (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + synchronized (mDeviceStateLock) { + mDeviceInventory.onSetA2dpSourceConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + } break; case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: - mDeviceInventory.onSetHearingAidConnectionState( - (BluetoothDevice) msg.obj, msg.arg1); + synchronized (mDeviceStateLock) { + mDeviceInventory.onSetHearingAidConnectionState( + (BluetoothDevice) msg.obj, msg.arg1); + } break; case MSG_BT_HEADSET_CNCT_FAILED: - mBtHelper.resetBluetoothSco(); + synchronized (mDeviceStateLock) { + mBtHelper.resetBluetoothSco(); + } break; case MSG_IL_BTA2DP_DOCK_TIMEOUT: // msg.obj == address of BTA2DP device - mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); + synchronized (mDeviceStateLock) { + mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); + } break; case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: final int a2dpCodec; final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; - synchronized (mA2dpAvrcpLock) { + synchronized (mDeviceStateLock) { a2dpCodec = mBtHelper.getA2dpCodec(btDevice); + mDeviceInventory.onBluetoothA2dpDeviceConfigChange( + new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec)); } - mDeviceInventory.onBluetoothA2dpDeviceConfigChange( - new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec)); break; case MSG_BROADCAST_AUDIO_BECOMING_NOISY: onSendBecomingNoisyIntent(); break; case MSG_II_SET_HEARING_AID_VOLUME: - mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2); + synchronized (mDeviceStateLock) { + mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2); + } break; case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME: - mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); + synchronized (mDeviceStateLock) { + mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); + } break; case MSG_I_DISCONNECT_BT_SCO: - mBtHelper.disconnectBluetoothSco(msg.arg1); + synchronized (mDeviceStateLock) { + mBtHelper.disconnectBluetoothSco(msg.arg1); + } break; case MSG_TOGGLE_HDMI: - mDeviceInventory.onToggleHdmi(); + synchronized (mDeviceStateLock) { + mDeviceInventory.onToggleHdmi(); + } break; case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: - mDeviceInventory.onBluetoothA2dpActiveDeviceChange( - (BtHelper.BluetoothA2dpDeviceInfo) msg.obj); + synchronized (mDeviceStateLock) { + mDeviceInventory.onBluetoothA2dpActiveDeviceChange( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj); + } break; default: Log.wtf(TAG, "Invalid message " + msg.what); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 97649a741f62a..11fdc8f050332 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -162,10 +162,7 @@ public final class AudioDeviceInventory { "A2DP sink connected: device addr=" + address + " state=" + state + " vol=" + a2dpVolume)); - final int a2dpCodec; - synchronized (mDeviceBroker.mA2dpAvrcpLock) { - a2dpCodec = btInfo.getCodec(); - } + final int a2dpCodec = btInfo.getCodec(); synchronized (mConnectedDevices) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, @@ -508,22 +505,20 @@ public final class AudioDeviceInventory { /*package*/ void disconnectA2dp() { synchronized (mConnectedDevices) { - synchronized (mDeviceBroker.mA2dpAvrcpLock) { - final ArraySet toRemove = new ArraySet<>(); - // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices - mConnectedDevices.values().forEach(deviceInfo -> { - if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - toRemove.add(deviceInfo.mDeviceAddress); - } - }); - if (toRemove.size() > 0) { - final int delay = checkSendBecomingNoisyIntentInt( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - 0, AudioSystem.DEVICE_NONE); - toRemove.stream().forEach(deviceAddress -> - makeA2dpDeviceUnavailableLater(deviceAddress, delay) - ); + final ArraySet toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + makeA2dpDeviceUnavailableLater(deviceAddress, delay) + ); } } } @@ -543,22 +538,20 @@ public final class AudioDeviceInventory { /*package*/ void disconnectHearingAid() { synchronized (mConnectedDevices) { - synchronized (mDeviceBroker.mHearingAidLock) { - final ArraySet toRemove = new ArraySet<>(); - // Disconnect ALL DEVICE_OUT_HEARING_AID devices - mConnectedDevices.values().forEach(deviceInfo -> { - if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { - toRemove.add(deviceInfo.mDeviceAddress); - } - }); - if (toRemove.size() > 0) { - final int delay = checkSendBecomingNoisyIntentInt( - AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); - toRemove.stream().forEach(deviceAddress -> - // TODO delay not used? - makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) - ); + final ArraySet toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_HEARING_AID devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { + toRemove.add(deviceInfo.mDeviceAddress); } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + // TODO delay not used? + makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) + ); } } } @@ -566,7 +559,8 @@ public final class AudioDeviceInventory { // must be called before removing the device from mConnectedDevices // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying // from AudioSystem - /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { + /*package*/ int checkSendBecomingNoisyIntent(int device, + @AudioService.ConnectionState int state, int musicDevice) { synchronized (mConnectedDevices) { return checkSendBecomingNoisyIntentInt(device, state, musicDevice); } @@ -840,8 +834,9 @@ public final class AudioDeviceInventory { // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying // from AudioSystem @GuardedBy("mConnectedDevices") - private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) { - if (state != 0) { + private int checkSendBecomingNoisyIntentInt(int device, + @AudioService.ConnectionState int state, int musicDevice) { + if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { return 0; } if ((device & mBecomingNoisyIntentDevices) == 0) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9457fe3d84f10..251cb61c3de83 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -786,7 +786,6 @@ public class AudioService extends IAudioService.Stub mPrescaleAbsoluteVolume[i] = preScale[i]; } } - } public void systemReady() { @@ -3821,7 +3820,6 @@ public class AudioService extends IAudioService.Stub private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { - if (existingMsgPolicy == SENDMSG_REPLACE) { handler.removeMessages(msg); } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index bf325013c7da6..95df21e729e42 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -16,6 +16,7 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -35,8 +36,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; -import com.android.internal.annotations.GuardedBy; - import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -57,21 +56,40 @@ public class BtHelper { } // List of clients having issued a SCO start request - private final ArrayList mScoClients = new ArrayList(); + private final @NonNull ArrayList mScoClients = new ArrayList(); // BluetoothHeadset API to control SCO connection - private BluetoothHeadset mBluetoothHeadset; + private @Nullable BluetoothHeadset mBluetoothHeadset; // Bluetooth headset device - private BluetoothDevice mBluetoothHeadsetDevice; + private @Nullable BluetoothDevice mBluetoothHeadsetDevice; + + private @Nullable BluetoothHearingAid mHearingAid; + + // Reference to BluetoothA2dp to query for AbsoluteVolume. + private @Nullable BluetoothA2dp mA2dp; + + // If absolute volume is supported in AVRCP device + private boolean mAvrcpAbsVolSupported = false; + + // Current connection state indicated by bluetooth headset + private int mScoConnectionState; // Indicate if SCO audio connection is currently active and if the initiator is // audio service (internal) or bluetooth headset (external) private int mScoAudioState; + + // Indicates the mode used for SCO audio connection. The mode is virtual call if the request + // originated from an app targeting an API version before JB MR2 and raw audio after that. + private int mScoAudioMode; + // SCO audio state is not active private static final int SCO_STATE_INACTIVE = 0; // SCO audio activation request waiting for headset service to connect private static final int SCO_STATE_ACTIVATE_REQ = 1; + // SCO audio state is active due to an action in BT handsfree (either voice recognition or + // in call audio) + private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; // SCO audio state is active or starting due to a request from AudioManager API private static final int SCO_STATE_ACTIVE_INTERNAL = 3; // SCO audio deactivation request waiting for headset service to connect @@ -79,39 +97,19 @@ public class BtHelper { // SCO audio deactivation in progress, waiting for Bluetooth audio intent private static final int SCO_STATE_DEACTIVATING = 5; - // SCO audio state is active due to an action in BT handsfree (either voice recognition or - // in call audio) - private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; - - // Indicates the mode used for SCO audio connection. The mode is virtual call if the request - // originated from an app targeting an API version before JB MR2 and raw audio after that. - private int mScoAudioMode; // SCO audio mode is undefined - /*package*/ static final int SCO_MODE_UNDEFINED = -1; + /*package*/ static final int SCO_MODE_UNDEFINED = -1; // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) private static final int SCO_MODE_RAW = 1; // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) private static final int SCO_MODE_VR = 2; - + // max valid SCO audio mode values private static final int SCO_MODE_MAX = 2; - // Current connection state indicated by bluetooth headset - private int mScoConnectionState; - private static final int BT_HEARING_AID_GAIN_MIN = -128; - @GuardedBy("mDeviceBroker.mHearingAidLock") - private BluetoothHearingAid mHearingAid; - - // Reference to BluetoothA2dp to query for AbsoluteVolume. - @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") - private BluetoothA2dp mA2dp; - // If absolute volume is supported in AVRCP device - @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") - private boolean mAvrcpAbsVolSupported = false; - //---------------------------------------------------------------------- /*package*/ static class BluetoothA2dpDeviceInfo { private final @NonNull BluetoothDevice mBtDevice; @@ -144,7 +142,7 @@ public class BtHelper { //---------------------------------------------------------------------- // Interface for AudioDeviceBroker - /*package*/ void onSystemReady() { + /*package*/ synchronized void onSystemReady() { mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; resetBluetoothSco(); getBluetoothHeadset(); @@ -165,45 +163,39 @@ public class BtHelper { } } - @GuardedBy("mBluetoothA2dpEnabledLock") - /*package*/ void onAudioServerDiedRestoreA2dp() { + /*package*/ synchronized void onAudioServerDiedRestoreA2dp() { final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); } - @GuardedBy("mA2dpAvrcpLock") - /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() { return (mA2dp != null && mAvrcpAbsVolSupported); } - @GuardedBy("mA2dpAvrcpLock") - /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) { mAvrcpAbsVolSupported = supported; } - /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) { - synchronized (mDeviceBroker.mA2dpAvrcpLock) { - if (mA2dp == null) { - if (AudioService.DEBUG_VOL) { - Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp"); - return; - } - } - if (!mAvrcpAbsVolSupported) { + /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) { + if (mA2dp == null) { + if (AudioService.DEBUG_VOL) { + Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp"); return; } - if (AudioService.DEBUG_VOL) { - Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); - } - AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( - AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10)); - mA2dp.setAvrcpAbsoluteVolume(index / 10); } + if (!mAvrcpAbsVolSupported) { + return; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10)); + mA2dp.setAvrcpAbsoluteVolume(index / 10); } - @GuardedBy("mA2dpAvrcpLock") - /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + /*package*/ synchronized int getA2dpCodec(@NonNull BluetoothDevice device) { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } @@ -218,7 +210,7 @@ public class BtHelper { return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); } - /*package*/ void receiveBtEvent(Intent intent) { + /*package*/ synchronized void receiveBtEvent(Intent intent) { final String action = intent.getAction(); if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); @@ -226,53 +218,51 @@ public class BtHelper { } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - synchronized (mScoClients) { - int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - // broadcast intent if the connection was initated by AudioService - if (!mScoClients.isEmpty() - && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL - || mScoAudioState == SCO_STATE_ACTIVATE_REQ - || mScoAudioState == SCO_STATE_DEACTIVATE_REQ - || mScoAudioState == SCO_STATE_DEACTIVATING)) { - broadcast = true; - } - switch (btState) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + // broadcast intent if the connection was initated by AudioService + if (!mScoClients.isEmpty() + && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL + || mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATING)) { + broadcast = true; + } + switch (btState) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // startBluetoothSco called after stopBluetoothSco + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcast = false; + break; } - mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent"); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // startBluetoothSco called after stopBluetoothSco - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcast = false; - break; - } - } - // Tear down SCO if disconnected from external - clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - break; - default: - // do not broadcast CONNECTING or invalid state - broadcast = false; - break; - } + } + // Tear down SCO if disconnected from external + clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + // do not broadcast CONNECTING or invalid state + broadcast = false; + break; } if (broadcast) { broadcastScoConnectionState(scoAudioState); @@ -289,15 +279,13 @@ public class BtHelper { * * @return false if SCO isn't connected */ - /*package*/ boolean isBluetoothScoOn() { - synchronized (mScoClients) { - if ((mBluetoothHeadset != null) - && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { - Log.w(TAG, "isBluetoothScoOn(true) returning false because " - + mBluetoothHeadsetDevice + " is not in audio connected mode"); - return false; - } + /*package*/ synchronized boolean isBluetoothScoOn() { + if ((mBluetoothHeadset != null) + && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { + Log.w(TAG, "isBluetoothScoOn(true) returning false because " + + mBluetoothHeadsetDevice + " is not in audio connected mode"); + return false; } return true; } @@ -308,17 +296,15 @@ public class BtHelper { * * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept */ - /*package*/ void disconnectBluetoothSco(int exceptPid) { - synchronized (mScoClients) { - checkScoAudioState(); - if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { - return; - } - clearAllScoClients(exceptPid, true); + /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) { + checkScoAudioState(); + if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { + return; } + clearAllScoClients(exceptPid, true); } - /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode, + /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode, @NonNull String eventSource) { ScoClient client = getScoClient(cb, true); // The calling identity must be cleared before calling ScoClient.incCount(). @@ -337,7 +323,8 @@ public class BtHelper { Binder.restoreCallingIdentity(ident); } - /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) { + /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb, + @NonNull String eventSource) { ScoClient client = getScoClient(cb, false); // The calling identity must be cleared before calling ScoClient.decCount(). // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs @@ -352,36 +339,29 @@ public class BtHelper { } - /*package*/ void setHearingAidVolume(int index, int streamType) { - synchronized (mDeviceBroker.mHearingAidLock) { - if (mHearingAid == null) { - if (AudioService.DEBUG_VOL) { - Log.i(TAG, "setHearingAidVolume: null mHearingAid"); - } - return; - } - //hearing aid expect volume value in range -128dB to 0dB - int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, - AudioSystem.DEVICE_OUT_HEARING_AID); - if (gainDB < BT_HEARING_AID_GAIN_MIN) { - gainDB = BT_HEARING_AID_GAIN_MIN; - } + /*package*/ synchronized void setHearingAidVolume(int index, int streamType) { + if (mHearingAid == null) { if (AudioService.DEBUG_VOL) { - Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" - + index + " gain=" + gainDB); + Log.i(TAG, "setHearingAidVolume: null mHearingAid"); } - AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( - AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); - mHearingAid.setVolume(gainDB); + return; } + //hearing aid expect volume value in range -128dB to 0dB + int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, + AudioSystem.DEVICE_OUT_HEARING_AID); + if (gainDB < BT_HEARING_AID_GAIN_MIN) { + gainDB = BT_HEARING_AID_GAIN_MIN; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" + + index + " gain=" + gainDB); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); + mHearingAid.setVolume(gainDB); } - //---------------------------------------------------------------------- - private void broadcastScoConnectionState(int state) { - mDeviceBroker.broadcastScoConnectionState(state); - } - - /*package*/ void onBroadcastScoConnectionState(int state) { + /*package*/ synchronized void onBroadcastScoConnectionState(int state) { if (state == mScoConnectionState) { return; } @@ -393,6 +373,26 @@ public class BtHelper { mScoConnectionState = state; } + /*package*/ synchronized void disconnectAllBluetoothProfiles() { + mDeviceBroker.handleDisconnectA2dp(); + mDeviceBroker.handleDisconnectA2dpSink(); + disconnectHeadset(); + mDeviceBroker.handleDisconnectHearingAid(); + } + + /*package*/ synchronized void resetBluetoothSco() { + clearAllScoClients(0, false); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + AudioSystem.setParameters("A2dpSuspended=false"); + mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); + } + + //---------------------------------------------------------------------- + private void broadcastScoConnectionState(int state) { + mDeviceBroker.broadcastScoConnectionState(state); + } + private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { if (btDevice == null) { return true; @@ -437,25 +437,23 @@ public class BtHelper { } private void setBtScoActiveDevice(BluetoothDevice btDevice) { - synchronized (mScoClients) { - Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); - final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; - if (Objects.equals(btDevice, previousActiveDevice)) { - return; - } - if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { - Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " - + previousActiveDevice); - } - if (!handleBtScoActiveDeviceChange(btDevice, true)) { - Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); - // set mBluetoothHeadsetDevice to null when failing to add new device - btDevice = null; - } - mBluetoothHeadsetDevice = btDevice; - if (mBluetoothHeadsetDevice == null) { - resetBluetoothSco(); - } + Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); + final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; + if (Objects.equals(btDevice, previousActiveDevice)) { + return; + } + if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { + Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " + + previousActiveDevice); + } + if (!handleBtScoActiveDeviceChange(btDevice, true)) { + Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); + // set mBluetoothHeadsetDevice to null when failing to add new device + btDevice = null; + } + mBluetoothHeadsetDevice = btDevice; + if (mBluetoothHeadsetDevice == null) { + resetBluetoothSco(); } } @@ -466,7 +464,7 @@ public class BtHelper { List deviceList; switch(profile) { case BluetoothProfile.A2DP: - synchronized (mDeviceBroker.mA2dpAvrcpLock) { + synchronized (BtHelper.this) { mA2dp = (BluetoothA2dp) proxy; deviceList = mA2dp.getConnectedDevices(); if (deviceList.size() > 0) { @@ -495,7 +493,7 @@ public class BtHelper { break; case BluetoothProfile.HEADSET: - synchronized (mScoClients) { + synchronized (BtHelper.this) { // Discard timeout message mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); mBluetoothHeadset = (BluetoothHeadset) proxy; @@ -536,17 +534,19 @@ public class BtHelper { break; case BluetoothProfile.HEARING_AID: - mHearingAid = (BluetoothHearingAid) proxy; - deviceList = mHearingAid.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - final @BluetoothProfile.BtProfileState int state = - mHearingAid.getConnectionState(btDevice); - mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( - btDevice, state, - /*suppressNoisyIntent*/ false, - /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE, - /*eventSource*/ "mBluetoothProfileServiceListener"); + synchronized (BtHelper.this) { + mHearingAid = (BluetoothHearingAid) proxy; + deviceList = mHearingAid.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + mHearingAid.getConnectionState(btDevice); + mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + btDevice, state, + /*suppressNoisyIntent*/ false, + /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE, + /*eventSource*/ "mBluetoothProfileServiceListener"); + } } break; @@ -579,18 +579,9 @@ public class BtHelper { } }; - void disconnectAllBluetoothProfiles() { - mDeviceBroker.handleDisconnectA2dp(); - mDeviceBroker.handleDisconnectA2dpSink(); - disconnectHeadset(); - mDeviceBroker.handleDisconnectHearingAid(); - } - private void disconnectHeadset() { - synchronized (mScoClients) { - setBtScoActiveDevice(null); - mBluetoothHeadset = null; - } + setBtScoActiveDevice(null); + mBluetoothHeadset = null; } //---------------------------------------------------------------------- @@ -605,8 +596,12 @@ public class BtHelper { mStartcount = 0; } + @Override public void binderDied() { - synchronized (mScoClients) { + // this is the only place the implementation of ScoClient needs to be synchronized + // on the instance, as all other methods are directly or indirectly called from + // package-private methods, which are synchronized + synchronized (BtHelper.this) { Log.w(TAG, "SCO client died"); int index = mScoClients.indexOf(this); if (index < 0) { @@ -618,77 +613,69 @@ public class BtHelper { } } - public void incCount(int scoAudioMode) { - synchronized (mScoClients) { - requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); - if (mStartcount == 0) { - try { - mCb.linkToDeath(this, 0); - } catch (RemoteException e) { - // client has already died! - Log.w(TAG, "ScoClient incCount() could not link to " - + mCb + " binder death"); - } - } - mStartcount++; - } - } - - public void decCount() { - synchronized (mScoClients) { - if (mStartcount == 0) { - Log.w(TAG, "ScoClient.decCount() already 0"); - } else { - mStartcount--; - if (mStartcount == 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "decCount() going to 0 but not registered to binder"); - } - } - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + void incCount(int scoAudioMode) { + requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); + if (mStartcount == 0) { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "ScoClient incCount() could not link to " + + mCb + " binder death"); } } + mStartcount++; } - public void clearCount(boolean stopSco) { - synchronized (mScoClients) { - if (mStartcount != 0) { + void decCount() { + if (mStartcount == 0) { + Log.w(TAG, "ScoClient.decCount() already 0"); + } else { + mStartcount--; + if (mStartcount == 0) { try { mCb.unlinkToDeath(this, 0); } catch (NoSuchElementException e) { - Log.w(TAG, "clearCount() mStartcount: " - + mStartcount + " != 0 but not registered to binder"); + Log.w(TAG, "decCount() going to 0 but not registered to binder"); } } - mStartcount = 0; - if (stopSco) { - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); } } - public int getCount() { + void clearCount(boolean stopSco) { + if (mStartcount != 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "clearCount() mStartcount: " + + mStartcount + " != 0 but not registered to binder"); + } + } + mStartcount = 0; + if (stopSco) { + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + + int getCount() { return mStartcount; } - public IBinder getBinder() { + IBinder getBinder() { return mCb; } - public int getPid() { + int getPid() { return mCreatorPid; } - public int totalCount() { - synchronized (mScoClients) { - int count = 0; - for (ScoClient mScoClient : mScoClients) { - count += mScoClient.getCount(); - } - return count; + private int totalCount() { + int count = 0; + for (ScoClient mScoClient : mScoClients) { + count += mScoClient.getCount(); } + return count; } private void requestScoState(int state, int scoAudioMode) { @@ -705,6 +692,7 @@ public class BtHelper { broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); // Accept SCO audio activation only in NORMAL audio mode or if the mode is // currently controlled by the same client process. + // TODO do not sync that way, see b/123769055 synchronized (mDeviceBroker.mSetModeLock) { int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty() ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid(); @@ -857,61 +845,44 @@ public class BtHelper { } } - /*package*/ void resetBluetoothSco() { - synchronized (mScoClients) { - clearAllScoClients(0, false); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - AudioSystem.setParameters("A2dpSuspended=false"); - mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); - } - - private void checkScoAudioState() { - synchronized (mScoClients) { - if (mBluetoothHeadset != null - && mBluetoothHeadsetDevice != null - && mScoAudioState == SCO_STATE_INACTIVE - && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } + if (mBluetoothHeadset != null + && mBluetoothHeadsetDevice != null + && mScoAudioState == SCO_STATE_INACTIVE + && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; } } private ScoClient getScoClient(IBinder cb, boolean create) { - synchronized (mScoClients) { - for (ScoClient existingClient : mScoClients) { - if (existingClient.getBinder() == cb) { - return existingClient; - } + for (ScoClient existingClient : mScoClients) { + if (existingClient.getBinder() == cb) { + return existingClient; } - if (create) { - ScoClient newClient = new ScoClient(cb); - mScoClients.add(newClient); - return newClient; - } - return null; } + if (create) { + ScoClient newClient = new ScoClient(cb); + mScoClients.add(newClient); + return newClient; + } + return null; } private void clearAllScoClients(int exceptPid, boolean stopSco) { - synchronized (mScoClients) { - ScoClient savedClient = null; - for (ScoClient cl : mScoClients) { - if (cl.getPid() != exceptPid) { - cl.clearCount(stopSco); - } else { - savedClient = cl; - } - } - mScoClients.clear(); - if (savedClient != null) { - mScoClients.add(savedClient); + ScoClient savedClient = null; + for (ScoClient cl : mScoClients) { + if (cl.getPid() != exceptPid) { + cl.clearCount(stopSco); + } else { + savedClient = cl; } } + mScoClients.clear(); + if (savedClient != null) { + mScoClients.add(savedClient); + } } private boolean getBluetoothHeadset() {