Merge changes from topic "presubmit-am-ff7699f855c14d1ca2c8a3438a9402f0" into sc-v2-dev-plus-aosp

* changes:
  [conflict] audioservice: improve SCO audio control logic 2p: a0b2ee74b2
  audioservice: improve SCO audio control logic
This commit is contained in:
Eric Laurent
2022-03-24 18:23:50 +00:00
committed by Android (Google) Code Review
2 changed files with 222 additions and 88 deletions

View File

@@ -251,8 +251,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
return;
}
}
setCommunicationRouteForClient(
cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource);
postSetCommunicationRouteForClient(new CommunicationClientInfo(
cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource));
}
}
}
@@ -282,8 +282,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
return false;
}
}
setCommunicationRouteForClient(
cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource);
postSetCommunicationRouteForClient(new CommunicationClientInfo(
cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource));
}
}
return true;
@@ -347,26 +347,35 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
/**
* Returns the device currently requested for communication use case.
* If the current audio mode owner is in the communication route client list,
* use this preference.
* Otherwise use first client's preference (first client corresponds to latest request).
* null is returned if no client is in the list.
* @return AudioDeviceAttributes the requested device for communication.
* Returns the communication client with the highest priority:
* - 1) the client which is currently also controlling the audio mode
* - 2) the first client in the stack if there is no audio mode owner
* - 3) no client otherwise
* @return CommunicationRouteClient the client driving the communication use case routing.
*/
@GuardedBy("mDeviceStateLock")
private AudioDeviceAttributes requestedCommunicationDevice() {
AudioDeviceAttributes device = null;
for (CommunicationRouteClient cl : mCommunicationRouteClients) {
if (cl.getPid() == mModeOwnerPid) {
device = cl.getDevice();
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
if (crc.getPid() == mModeOwnerPid) {
return crc;
}
}
if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
device = mCommunicationRouteClients.get(0).getDevice();
return mCommunicationRouteClients.get(0);
}
return null;
}
/**
* Returns the device currently requested for communication use case.
* Use the device requested by the communication route client selected by
* {@link #topCommunicationRouteClient()} if any or none otherwise.
* @return AudioDeviceAttributes the requested device for communication.
*/
@GuardedBy("mDeviceStateLock")
private AudioDeviceAttributes requestedCommunicationDevice() {
CommunicationRouteClient crc = topCommunicationRouteClient();
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "requestedCommunicationDevice, device: "
+ device + " mode owner pid: " + mModeOwnerPid);
@@ -696,7 +705,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
synchronized (mDeviceStateLock) {
mBluetoothScoOn = on;
sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
postUpdateCommunicationRouteClient(eventSource);
}
}
@@ -756,7 +765,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
synchronized (mDeviceStateLock) {
AudioDeviceAttributes device =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource);
postSetCommunicationRouteForClient(new CommunicationClientInfo(
cb, pid, device, scoAudioMode, eventSource));
}
}
}
@@ -774,8 +785,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (client == null || !client.requestsBluetoothSco()) {
return;
}
setCommunicationRouteForClient(
cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource);
postSetCommunicationRouteForClient(new CommunicationClientInfo(
cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource));
}
}
}
@@ -976,6 +987,61 @@ import java.util.concurrent.atomic.AtomicBoolean;
MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset);
}
/*package*/ void postUpdateCommunicationRouteClient(String eventSource) {
sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
}
/*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) {
sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info);
}
/*package*/ void postScoAudioStateChanged(int state) {
sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
}
/*package*/ static final class CommunicationClientInfo {
final @NonNull IBinder mCb;
final int mPid;
final @NonNull AudioDeviceAttributes mDevice;
final int mScoAudioMode;
final @NonNull String mEventSource;
CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device,
int scoAudioMode, @NonNull String eventSource) {
mCb = cb;
mPid = pid;
mDevice = device;
mScoAudioMode = scoAudioMode;
mEventSource = eventSource;
}
// redefine equality op so we can match messages intended for this client
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (!(o instanceof CommunicationClientInfo)) {
return false;
}
return mCb.equals(((CommunicationClientInfo) o).mCb)
&& mPid == ((CommunicationClientInfo) o).mPid;
}
@Override
public String toString() {
return "CommunicationClientInfo mCb=" + mCb.toString()
+"mPid=" + mPid
+"mDevice=" + mDevice.toString()
+"mScoAudioMode=" + mScoAudioMode
+"mEventSource=" + mEventSource;
}
}
//---------------------------------------------------------------------
// Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
// only call from a "handle"* method or "on"* method
@@ -1252,18 +1318,30 @@ import java.util.concurrent.atomic.AtomicBoolean;
synchronized (mDeviceStateLock) {
mModeOwnerPid = msg.arg1;
if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
onUpdateCommunicationRoute("setNewModeOwner");
onUpdateCommunicationRouteClient("setNewModeOwner");
}
}
}
break;
case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj);
CommunicationClientInfo info = (CommunicationClientInfo) msg.obj;
setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice,
info.mScoAudioMode, info.mEventSource);
}
}
break;
case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
onUpdateCommunicationRouteClient((String) msg.obj);
}
}
break;
case MSG_L_UPDATE_COMMUNICATION_ROUTE:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
@@ -1271,6 +1349,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
break;
case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj);
}
}
break;
case MSG_I_SCO_AUDIO_STATE_CHANGED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.onScoAudioStateChanged(msg.arg1);
}
}
break;
case MSG_TOGGLE_HDMI:
synchronized (mDeviceStateLock) {
mDeviceInventory.onToggleHdmi();
@@ -1424,6 +1519,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
private static final int MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY = 40;
private static final int MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY = 41;
private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42;
private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
//
@@ -1642,9 +1740,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
return;
}
Log.w(TAG, "Communication client died");
setCommunicationRouteForClient(
client.getBinder(), client.getPid(), null, BtHelper.SCO_MODE_UNDEFINED,
"onCommunicationRouteClientDied");
removeCommunicationRouteClient(client.getBinder(), true);
onUpdateCommunicationRouteClient("onCommunicationRouteClientDied");
}
/**
@@ -1698,11 +1795,31 @@ import java.util.concurrent.atomic.AtomicBoolean;
AudioSystem.setParameters("BT_SCO=on");
}
if (preferredCommunicationDevice == null) {
postRemovePreferredDevicesForStrategy(mCommunicationStrategyId);
removePreferredDevicesForStrategySync(mCommunicationStrategyId);
} else {
postSetPreferredDevicesForStrategy(
setPreferredDevicesForStrategySync(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
}
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
}
/**
* Select new communication device from communication route client at the top of the stack
* and restore communication route including restarting SCO audio if needed.
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(String eventSource) {
onUpdateCommunicationRoute(eventSource);
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
+ crc + " eventSource: " + eventSource);
}
if (crc != null) {
setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
BtHelper.SCO_MODE_UNDEFINED, eventSource);
}
}
private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {

View File

@@ -103,7 +103,7 @@ public class BtHelper {
// SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
/*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0;
// SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
private static final int SCO_MODE_VR = 2;
private static final int SCO_MODE_VR = 2;
// max valid SCO audio mode values
private static final int SCO_MODE_MAX = 2;
@@ -304,68 +304,76 @@ public class BtHelper {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
setBtScoActiveDevice(btDevice);
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
Log.i(TAG, "receiveBtEvent ACTION_AUDIO_STATE_CHANGED: " + btState);
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;
} else if (mDeviceBroker.isBluetoothScoRequested()) {
// broadcast intent if the connection was initated by AudioService
broadcast = true;
}
mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
// There are two cases where we want to immediately reconnect audio:
// 1) If a new start request was received while disconnecting: this was
// notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
// 2) If audio was connected then disconnected via Bluetooth APIs and
// we still have pending activation requests by apps: this is indicated by
// state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
|| (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
&& mDeviceBroker.isBluetoothScoRequested())) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
&& connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
broadcast = true;
break;
}
}
if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
broadcast = true;
}
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:
break;
}
if (broadcast) {
broadcastScoConnectionState(scoAudioState);
//FIXME: this is to maintain compatibility with deprecated intent
// AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
sendStickyBroadcastToAll(newIntent);
}
Log.i(TAG,"receiveBtEvent ACTION_AUDIO_STATE_CHANGED: "+btState);
mDeviceBroker.postScoAudioStateChanged(btState);
}
}
/**
* Exclusively called from AudioDeviceBroker when handling MSG_I_SCO_AUDIO_STATE_CHANGED
* as part of the serialization of the communication route selection
*/
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
void onScoAudioStateChanged(int state) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
switch (state) {
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;
} else if (mDeviceBroker.isBluetoothScoRequested()) {
// broadcast intent if the connection was initated by AudioService
broadcast = true;
}
mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
// There are two cases where we want to immediately reconnect audio:
// 1) If a new start request was received while disconnecting: this was
// notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
// 2) If audio was connected then disconnected via Bluetooth APIs and
// we still have pending activation requests by apps: this is indicated by
// state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
&& connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
broadcast = true;
break;
}
}
if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
broadcast = true;
}
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:
break;
}
if(broadcast) {
broadcastScoConnectionState(scoAudioState);
//FIXME: this is to maintain compatibility with deprecated intent
// AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
sendStickyBroadcastToAll(newIntent);
}
}
/**
*
* @return false if SCO isn't connected
@@ -744,6 +752,15 @@ public class BtHelper {
case SCO_STATE_ACTIVE_INTERNAL:
Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
break;
case SCO_STATE_ACTIVE_EXTERNAL:
/* Confirm SCO Audio connection to requesting app as it is already connected
* externally (i.e. through SCO APIs by Telecom service).
* Once SCO Audio is disconnected by the external owner, we will reconnect it
* automatically on behalf of the requesting app and the state will move to
* SCO_STATE_ACTIVE_INTERNAL.
*/
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
break;
default:
Log.w(TAG, "requestScoState: failed to connect in state "
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode);