AudioService: fix overlapping speakerphone + Bluetooth SCO routing requests

VoIP apps sometimes send conflicting requests to route call audio
to both speaker phone and BT SCO. This can happen if the app does not
reset the speakerphone request properly before requesting BT SCO.

In this case we must make sure to cancel the oldest request so that the
correct routing is restored when the audio mode owner is updated.

Bug: 162527933
Test: repro steps in bug
Test: regression tests with speakerphone and BT routes with major
VoIP apps.

Change-Id: I2d390a283822732474583ad655dbc760b0ba8c66
This commit is contained in:
Eric Laurent
2020-08-04 14:22:03 -07:00
parent 4fc108418a
commit 7647273fe6
2 changed files with 72 additions and 9 deletions

View File

@@ -224,12 +224,35 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (!addSpeakerphoneClient(cb, pid, on)) { if (!addSpeakerphoneClient(cb, pid, on)) {
return false; return false;
} }
if (on) {
// Cancel BT SCO ON request by this same client: speakerphone and BT SCO routes
// are mutually exclusive.
// See symmetrical operation for startBluetoothScoForClient_Sync().
mBtHelper.stopBluetoothScoForPid(pid);
}
final boolean wasOn = isSpeakerphoneOn(); final boolean wasOn = isSpeakerphoneOn();
updateSpeakerphoneOn(eventSource); updateSpeakerphoneOn(eventSource);
return (wasOn != isSpeakerphoneOn()); return (wasOn != isSpeakerphoneOn());
} }
} }
/**
* Turns speakerphone off for a given pid and update speakerphone state.
* @param pid
*/
@GuardedBy("mDeviceStateLock")
private void setSpeakerphoneOffForPid(int pid) {
SpeakerphoneClient client = getSpeakerphoneClientForPid(pid);
if (client == null) {
return;
}
client.unregisterDeathRecipient();
mSpeakerphoneClients.remove(client);
final String eventSource = new StringBuilder("setSpeakerphoneOffForPid(")
.append(pid).append(")").toString();
updateSpeakerphoneOn(eventSource);
}
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private void updateSpeakerphoneOn(String eventSource) { private void updateSpeakerphoneOn(String eventSource) {
if (isSpeakerphoneOnRequested()) { if (isSpeakerphoneOnRequested()) {
@@ -488,6 +511,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
/*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode, /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
@NonNull String eventSource) { @NonNull String eventSource) {
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
// Cancel speakerphone ON request by this same client: speakerphone and BT SCO routes
// are mutually exclusive.
// See symmetrical operation for setSpeakerphoneOn(true).
setSpeakerphoneOffForPid(Binder.getCallingPid());
mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource); mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
} }
} }
@@ -1379,6 +1406,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
return false; return false;
} }
@GuardedBy("mDeviceStateLock")
private SpeakerphoneClient getSpeakerphoneClientForPid(int pid) {
for (SpeakerphoneClient cl : mSpeakerphoneClients) {
if (cl.getPid() == pid) {
return cl;
}
}
return null;
}
// List of clients requesting speakerPhone ON // List of clients requesting speakerPhone ON
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients = private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =

View File

@@ -432,19 +432,35 @@ public class BtHelper {
// and this must be done on behalf of system server to make sure permissions are granted. // and this must be done on behalf of system server to make sure permissions are granted.
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
if (client != null) { if (client != null) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); stopAndRemoveClient(client, eventSource);
client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
SCO_MODE_VIRTUAL_CALL);
// If a disconnection is pending, the client will be removed whne clearAllScoClients()
// is called form receiveBtEvent()
if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
&& mScoAudioState != SCO_STATE_DEACTIVATING) {
client.remove(false /*stop */, true /*unregister*/);
}
} }
Binder.restoreCallingIdentity(ident); Binder.restoreCallingIdentity(ident);
} }
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void stopBluetoothScoForPid(int pid) {
ScoClient client = getScoClientForPid(pid);
if (client == null) {
return;
}
final String eventSource = new StringBuilder("stopBluetoothScoForPid(")
.append(pid).append(")").toString();
stopAndRemoveClient(client, eventSource);
}
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
SCO_MODE_VIRTUAL_CALL);
// If a disconnection is pending, the client will be removed when clearAllScoClients()
// is called form receiveBtEvent()
if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
&& mScoAudioState != SCO_STATE_DEACTIVATING) {
client.remove(false /*stop */, true /*unregister*/);
}
}
/*package*/ synchronized void setHearingAidVolume(int index, int streamType) { /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
if (mHearingAid == null) { if (mHearingAid == null) {
@@ -974,6 +990,16 @@ public class BtHelper {
return null; return null;
} }
@GuardedBy("BtHelper.this")
private ScoClient getScoClientForPid(int pid) {
for (ScoClient cl : mScoClients) {
if (cl.getPid() == pid) {
return cl;
}
}
return null;
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock") // @GuardedBy("AudioDeviceBroker.mSetModeLock")
//@GuardedBy("AudioDeviceBroker.mDeviceStateLock") //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
@GuardedBy("BtHelper.this") @GuardedBy("BtHelper.this")