AudioService: refactor communication route control am: ec51aa8207 am: ad87098233

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1534431

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Id01d7af0da83c4acc44303cb77e1858914a25687
This commit is contained in:
Eric Laurent
2020-12-23 13:31:15 +00:00
committed by Automerger Merge Worker
3 changed files with 465 additions and 521 deletions

View File

@@ -25,6 +25,8 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.media.AudioDeviceAttributes; import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioRoutesInfo; import android.media.AudioRoutesInfo;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.media.IAudioRoutesObserver; import android.media.IAudioRoutesObserver;
@@ -39,6 +41,7 @@ import android.os.Message;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.PrintWriterPrinter; import android.util.PrintWriterPrinter;
@@ -46,8 +49,8 @@ import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
@@ -73,10 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/** Forced device usage for communications sent to AudioSystem */ /** Forced device usage for communications sent to AudioSystem */
private int mForcedUseForComm; private int mForcedUseForComm;
/**
* Externally reported force device usage state returned by getters: always consistent
* with requests by setters */
private int mForcedUseForCommExt;
// Manages all connected devices, only ever accessed on the message loop // Manages all connected devices, only ever accessed on the message loop
private final AudioDeviceInventory mDeviceInventory; private final AudioDeviceInventory mDeviceInventory;
@@ -136,7 +135,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
setupMessaging(mContext); setupMessaging(mContext);
mForcedUseForComm = AudioSystem.FORCE_NONE; mForcedUseForComm = AudioSystem.FORCE_NONE;
mForcedUseForCommExt = mForcedUseForComm;
} }
/*package*/ Context getContext() { /*package*/ Context getContext() {
@@ -159,15 +157,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
/*package*/ void onAudioServerDied() { /*package*/ void onAudioServerDied() {
// Restore forced usage for communications and record
synchronized (mDeviceStateLock) {
AudioSystem.setParameters(
"BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm,
false /*fromA2dp*/, "onAudioServerDied");
onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm,
false /*fromA2dp*/, "onAudioServerDied");
}
// restore devices // restore devices
sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE); sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
} }
@@ -219,85 +208,156 @@ import java.util.concurrent.atomic.AtomicBoolean;
* Turns speakerphone on/off * Turns speakerphone on/off
* @param on * @param on
* @param eventSource for logging purposes * @param eventSource for logging purposes
* @return true if speakerphone state changed
*/ */
/*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
synchronized (mDeviceStateLock) {
if (!addSpeakerphoneClient(cb, pid, on)) { if (AudioService.DEBUG_COMM_RTE) {
return false; Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
}
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
AudioDeviceAttributes device = null;
if (on) {
device = new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
} else {
CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
if (client == null || !client.requestsSpeakerphone()) {
return;
}
}
setCommunicationRouteForClient(
cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource);
} }
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();
updateSpeakerphoneOn(eventSource);
return (wasOn != isSpeakerphoneOn());
} }
} }
/**
* Turns speakerphone off for a given pid and update speakerphone state.
* @param pid
*/
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private void setSpeakerphoneOffForPid(int pid) { /*package*/ void setCommunicationRouteForClient(
SpeakerphoneClient client = getSpeakerphoneClientForPid(pid); IBinder cb, int pid, AudioDeviceAttributes device,
int scoAudioMode, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
}
final boolean wasBtScoRequested = isBluetoothScoRequested();
final boolean wasSpeakerphoneRequested = isSpeakerphoneRequested();
CommunicationRouteClient client;
// Save previous client route in case of failure to start BT SCO audio
AudioDeviceAttributes prevClientDevice = null;
client = getCommunicationRouteClientForPid(pid);
if (client != null) {
prevClientDevice = client.getDevice();
}
if (device != null) {
client = addCommunicationRouteClient(cb, pid, device);
if (client == null) {
Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
+ pid + " and device: " + device);
}
} else {
client = removeCommunicationRouteClient(cb, true);
}
if (client == null) { if (client == null) {
return; return;
} }
client.unregisterDeathRecipient();
mSpeakerphoneClients.remove(client);
final String eventSource = new StringBuilder("setSpeakerphoneOffForPid(")
.append(pid).append(")").toString();
updateSpeakerphoneOn(eventSource);
}
@GuardedBy("mDeviceStateLock") boolean isBtScoRequested = isBluetoothScoRequested();
private void updateSpeakerphoneOn(String eventSource) { if (isBtScoRequested && !wasBtScoRequested) {
if (isSpeakerphoneOnRequested()) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + pid);
// clean up or restore previous client selection
if (prevClientDevice != null) {
addCommunicationRouteClient(cb, pid, prevClientDevice);
} else {
removeCommunicationRouteClient(cb, true);
}
} }
mForcedUseForComm = AudioSystem.FORCE_SPEAKER; } else if (!isBtScoRequested && wasBtScoRequested) {
} else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { mBtHelper.stopBluetoothSco(eventSource);
if (mBtHelper.isBluetoothScoOn()) { }
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
setForceUse_Async( if (wasSpeakerphoneRequested != isSpeakerphoneRequested()) {
AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource); try {
} else { mContext.sendBroadcastAsUser(
mForcedUseForComm = AudioSystem.FORCE_NONE; new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL);
} catch (Exception e) {
Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
} }
} }
mForcedUseForCommExt = mForcedUseForComm;
setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
} }
/** /**
* Returns if speakerphone is requested ON or OFF. * Returns the device currently requested for communication use case.
* If the current audio mode owner is in the speakerphone client list, use this preference. * 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). * Otherwise use first client's preference (first client corresponds to latest request).
* Speakerphone is requested OFF if no client is in the list. * null is returned if no client is in the list.
* @return true if speakerphone is requested ON, false otherwise * @return AudioDeviceAttributes the requested device for communication.
*/ */
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private boolean isSpeakerphoneOnRequested() { private AudioDeviceAttributes requestedCommunicationDevice() {
if (mSpeakerphoneClients.isEmpty()) { AudioDeviceAttributes device = null;
return false; for (CommunicationRouteClient cl : mCommunicationRouteClients) {
}
for (SpeakerphoneClient cl : mSpeakerphoneClients) {
if (cl.getPid() == mModeOwnerPid) { if (cl.getPid() == mModeOwnerPid) {
return cl.isOn(); device = cl.getDevice();
} }
} }
return mSpeakerphoneClients.get(0).isOn(); if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
device = mCommunicationRouteClients.get(0).getDevice();
}
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "requestedCommunicationDevice, device: "
+ device + " mode owner pid: " + mModeOwnerPid);
}
return device;
} }
/*package*/ boolean isSpeakerphoneOn() { /**
* Helper method on top of requestedCommunicationDevice() indicating if
* speakerphone ON is currently requested or not.
* @return true if speakerphone ON requested, false otherwise.
*/
private boolean isSpeakerphoneRequested() {
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); AudioDeviceAttributes device = requestedCommunicationDevice();
return device != null
&& device.getType()
== AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
}
}
/**
* Indicates if active route selection for communication is speakerphone.
* @return true if speakerphone is active, false otherwise.
*/
/*package*/ boolean isSpeakerphoneOn() {
return getForcedUseForComm() == AudioSystem.FORCE_SPEAKER;
}
/**
* Helper method on top of requestedCommunicationDevice() indicating if
* Bluetooth SCO ON is currently requested or not.
* @return true if Bluetooth SCO ON is requested, false otherwise.
*/
/*package*/ boolean isBluetoothScoRequested() {
synchronized (mDeviceStateLock) {
AudioDeviceAttributes device = requestedCommunicationDevice();
return device != null
&& device.getType()
== AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
} }
} }
@@ -348,7 +408,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
/*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( /*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int a2dpVolume) { int profile, boolean suppressNoisyIntent, int a2dpVolume) {
@@ -431,42 +490,28 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
} }
// never called by system components
/*package*/ void setBluetoothScoOnByApp(boolean on) {
synchronized (mDeviceStateLock) {
mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
}
}
/*package*/ boolean isBluetoothScoOnForApp() { /**
synchronized (mDeviceStateLock) { * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO; */
} private boolean mBluetoothScoOn;
}
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) { /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
//Log.i(TAG, "setBluetoothScoOn: " + on + " " + eventSource); if (AudioService.DEBUG_COMM_RTE) {
synchronized (mDeviceStateLock) { Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
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 = isSpeakerphoneOnRequested()
? AudioSystem.FORCE_SPEAKER : 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 synchronized (mDeviceStateLock) {
mAudioService.postUpdateRingerModeServiceInt(); mBluetoothScoOn = on;
sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
}
}
/**
* Indicates if active route selection for communication is Bluetooth SCO.
* @return true if Bluetooth SCO is active , false otherwise.
*/
/*package*/ boolean isBluetoothScoOn() {
return getForcedUseForComm() == AudioSystem.FORCE_BT_SCO;
} }
/*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
@@ -509,22 +554,43 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
} }
@GuardedBy("mSetModeLock") /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
/*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
@NonNull String eventSource) { @NonNull String eventSource) {
synchronized (mDeviceStateLock) {
// Cancel speakerphone ON request by this same client: speakerphone and BT SCO routes if (AudioService.DEBUG_COMM_RTE) {
// are mutually exclusive. Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid);
// See symmetrical operation for setSpeakerphoneOn(true). }
setSpeakerphoneOffForPid(Binder.getCallingPid());
mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource); synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
AudioDeviceAttributes device = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_SCO, "");
setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource);
if (!isBluetoothScoRequested()) {
Log.w(TAG, "startBluetoothScoForClient_Sync: rejected for pid: "
+ pid + " mode owner pid: " + mModeOwnerPid);
postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
}
} }
} }
@GuardedBy("mSetModeLock") /*package*/ void stopBluetoothScoForClient(
/*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) { IBinder cb, int pid, @NonNull String eventSource) {
synchronized (mDeviceStateLock) {
mBtHelper.stopBluetoothScoForClient(cb, eventSource); if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid);
}
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
if (client == null || !client.requestsBluetoothSco()) {
return;
}
setCommunicationRouteForClient(
cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource);
}
} }
} }
@@ -696,12 +762,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
hearingAidProfile); hearingAidProfile);
} }
/*package*/ void postScoClientDied(Object obj) { /*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) {
sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj); sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client);
}
/*package*/ void postSpeakerphoneClientDied(Object obj) {
sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
} }
/*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy, /*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy,
@@ -821,15 +883,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.dump(pw, prefix); mDeviceInventory.dump(pw, prefix);
pw.println("\n" + prefix + "Communication route clients:");
mCommunicationRouteClients.forEach((cl) -> {
pw.println(" " + prefix + "pid: " + cl.getPid() + " device: "
+ cl.getDevice() + " cb: " + cl.getBinder()); });
pw.println("\n" + prefix + "mForcedUseForComm: " pw.println("\n" + prefix + "mForcedUseForComm: "
+ AudioSystem.forceUseConfigToString(mForcedUseForComm)); + AudioSystem.forceUseConfigToString(mForcedUseForComm));
pw.println(prefix + "mForcedUseForCommExt: "
+ AudioSystem.forceUseConfigToString(mForcedUseForCommExt));
pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid); pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid);
pw.println(prefix + "Speakerphone clients:");
mSpeakerphoneClients.forEach((cl) -> {
pw.println(" " + prefix + "pid: " + cl.getPid() + " on: "
+ cl.isOn() + " cb: " + cl.getBinder()); });
mBtHelper.dump(pw, prefix); mBtHelper.dump(pw, prefix);
} }
@@ -852,6 +913,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
.set(MediaMetrics.Property.FORCE_USE_MODE, .set(MediaMetrics.Property.FORCE_USE_MODE,
AudioSystem.forceUseConfigToString(config)) AudioSystem.forceUseConfigToString(config))
.record(); .record();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<"
+ fromA2dp + ">, eventSource<" + eventSource + ">)");
}
AudioSystem.setForceUse(useCase, config); AudioSystem.setForceUse(useCase, config);
} }
@@ -917,9 +983,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_RESTORE_DEVICES: case MSG_RESTORE_DEVICES:
synchronized (mDeviceStateLock) { synchronized (mSetModeLock) {
mDeviceInventory.onRestoreDevices(); synchronized (mDeviceStateLock) {
mBtHelper.onAudioServerDiedRestoreA2dp(); mDeviceInventory.onRestoreDevices();
mBtHelper.onAudioServerDiedRestoreA2dp();
onUpdateCommunicationRoute("MSG_RESTORE_DEVICES");
}
} }
break; break;
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
@@ -1009,25 +1078,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (mModeOwnerPid != msg.arg1) { if (mModeOwnerPid != msg.arg1) {
mModeOwnerPid = msg.arg1; mModeOwnerPid = msg.arg1;
if (msg.arg2 != AudioSystem.MODE_RINGTONE) { if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
updateSpeakerphoneOn("setNewModeOwner"); onUpdateCommunicationRoute("setNewModeOwner");
}
if (mModeOwnerPid != 0) {
mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
} }
} }
} }
} }
break; break;
case MSG_L_SCOCLIENT_DIED: case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
synchronized (mSetModeLock) { synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
mBtHelper.scoClientDied(msg.obj); onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj);
} }
} }
break; break;
case MSG_L_SPEAKERPHONE_CLIENT_DIED: case MSG_L_UPDATE_COMMUNICATION_ROUTE:
synchronized (mDeviceStateLock) { synchronized (mSetModeLock) {
speakerphoneClientDied(msg.obj); synchronized (mDeviceStateLock) {
onUpdateCommunicationRoute((String) msg.obj);
}
} }
break; break;
case MSG_TOGGLE_HDMI: case MSG_TOGGLE_HDMI:
@@ -1207,17 +1275,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
// process external command to (dis)connect a hearing aid device // process external command to (dis)connect a hearing aid device
private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31; private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
// a ScoClient died in BtHelper private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 32;
private static final int MSG_L_SCOCLIENT_DIED = 32; private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 33;
private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 33;
private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 34;
private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35; private static final int MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED = 34;
private static final int MSG_CHECK_MUTE_MUSIC = 36; private static final int MSG_CHECK_MUTE_MUSIC = 35;
private static final int MSG_REPORT_NEW_ROUTES_A2DP = 37; private static final int MSG_REPORT_NEW_ROUTES_A2DP = 36;
private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 38; private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37;
private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 39; private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
private static boolean isMessageHandledUnderWakelock(int msgId) { private static boolean isMessageHandledUnderWakelock(int msgId) {
@@ -1372,14 +1440,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
private class SpeakerphoneClient implements IBinder.DeathRecipient { // List of applications requesting a specific route for communication.
@GuardedBy("mDeviceStateLock")
private final @NonNull LinkedList<CommunicationRouteClient> mCommunicationRouteClients =
new LinkedList<CommunicationRouteClient>();
private class CommunicationRouteClient implements IBinder.DeathRecipient {
private final IBinder mCb; private final IBinder mCb;
private final int mPid; private final int mPid;
private final boolean mOn; private AudioDeviceAttributes mDevice;
SpeakerphoneClient(IBinder cb, int pid, boolean on) {
CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
mCb = cb; mCb = cb;
mPid = pid; mPid = pid;
mOn = on; mDevice = device;
} }
public boolean registerDeathRecipient() { public boolean registerDeathRecipient() {
@@ -1388,7 +1462,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mCb.linkToDeath(this, 0); mCb.linkToDeath(this, 0);
status = true; status = true;
} catch (RemoteException e) { } catch (RemoteException e) {
Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death"); Log.w(TAG, "CommunicationRouteClient could not link to " + mCb + " binder death");
} }
return status; return status;
} }
@@ -1397,13 +1471,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
try { try {
mCb.unlinkToDeath(this, 0); mCb.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
Log.w(TAG, "SpeakerphoneClient could not not unregistered to binder"); Log.w(TAG, "CommunicationRouteClient could not not unregistered to binder");
} }
} }
@Override @Override
public void binderDied() { public void binderDied() {
postSpeakerphoneClientDied(this); postCommunicationRouteClientDied(this);
} }
IBinder getBinder() { IBinder getBinder() {
@@ -1414,29 +1488,99 @@ import java.util.concurrent.atomic.AtomicBoolean;
return mPid; return mPid;
} }
boolean isOn() { AudioDeviceAttributes getDevice() {
return mOn; return mDevice;
}
boolean requestsBluetoothSco() {
return mDevice != null
&& mDevice.getType()
== AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
}
boolean requestsSpeakerphone() {
return mDevice != null
&& mDevice.getType()
== AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
} }
} }
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private void speakerphoneClientDied(Object obj) { private void onCommunicationRouteClientDied(CommunicationRouteClient client) {
if (obj == null) { if (client == null) {
return; return;
} }
Log.w(TAG, "Speaker client died"); Log.w(TAG, "Speaker client died");
if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) { if (removeCommunicationRouteClient(client.getBinder(), false)
updateSpeakerphoneOn("speakerphoneClientDied"); != null) {
onUpdateCommunicationRoute("onCommunicationRouteClientDied");
} }
} }
private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) { /**
for (SpeakerphoneClient cl : mSpeakerphoneClients) { * Determines which forced usage for communication should be sent to audio policy manager
* as a function of current SCO audio activation state and active communication route requests.
* SCO audio state has the highest priority as it can result from external activation by
* telephony service.
* @return selected forced usage for communication.
*/
@GuardedBy("mDeviceStateLock")
private int getForcedUseForComm() {
boolean btSCoOn = mBluetoothScoOn && mBtHelper.isBluetoothScoOn();
if (btSCoOn) {
return AudioSystem.FORCE_BT_SCO;
}
if (isSpeakerphoneRequested()) {
return AudioSystem.FORCE_SPEAKER;
}
return AudioSystem.FORCE_NONE;
}
/**
* Configures audio policy manager and audio HAL according to active communication route.
* Always called from message Handler.
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRoute(String eventSource) {
mForcedUseForComm = getForcedUseForComm();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRoute, mForcedUseForComm: " + mForcedUseForComm
+ " eventSource: " + eventSource);
}
if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
AudioSystem.setParameters("BT_SCO=on");
setForceUse_Async(
AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_BT_SCO, eventSource);
setForceUse_Async(
AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
} else {
AudioSystem.setParameters("BT_SCO=off");
setForceUse_Async(
AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
setForceUse_Async(
AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_SPEAKER, eventSource);
} else {
setForceUse_Async(
AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_NONE, eventSource);
}
}
mAudioService.postUpdateRingerModeServiceInt();
}
private CommunicationRouteClient removeCommunicationRouteClient(
IBinder cb, boolean unregister) {
for (CommunicationRouteClient cl : mCommunicationRouteClients) {
if (cl.getBinder() == cb) { if (cl.getBinder() == cb) {
if (unregister) { if (unregister) {
cl.unregisterDeathRecipient(); cl.unregisterDeathRecipient();
} }
mSpeakerphoneClients.remove(cl); mCommunicationRouteClients.remove(cl);
return cl; return cl;
} }
} }
@@ -1444,30 +1588,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) { private CommunicationRouteClient addCommunicationRouteClient(
IBinder cb, int pid, AudioDeviceAttributes device) {
// always insert new request at first position // always insert new request at first position
removeSpeakerphoneClient(cb, true); removeCommunicationRouteClient(cb, true);
SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on); CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
if (client.registerDeathRecipient()) { if (client.registerDeathRecipient()) {
mSpeakerphoneClients.add(0, client); mCommunicationRouteClients.add(0, client);
return true; return client;
} }
return false; return null;
} }
@GuardedBy("mDeviceStateLock") @GuardedBy("mDeviceStateLock")
private SpeakerphoneClient getSpeakerphoneClientForPid(int pid) { private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
for (SpeakerphoneClient cl : mSpeakerphoneClients) { for (CommunicationRouteClient cl : mCommunicationRouteClients) {
if (cl.getPid() == pid) { if (cl.getPid() == pid) {
return cl; return cl;
} }
} }
return null; return null;
} }
// List of clients requesting speakerPhone ON
@GuardedBy("mDeviceStateLock")
private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
new ArrayList<SpeakerphoneClient>();
} }

View File

@@ -207,6 +207,9 @@ public class AudioService extends IAudioService.Stub
/** debug calls to devices APIs */ /** debug calls to devices APIs */
protected static final boolean DEBUG_DEVICES = false; protected static final boolean DEBUG_DEVICES = false;
/** Debug communication route */
protected static final boolean DEBUG_COMM_RTE = false;
/** How long to delay before persisting a change in volume/ringer mode. */ /** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500; private static final int PERSIST_DELAY = 500;
@@ -3689,7 +3692,7 @@ public class AudioService extends IAudioService.Stub
final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
|| ringerMode == AudioManager.RINGER_MODE_SILENT; || ringerMode == AudioManager.RINGER_MODE_SILENT;
final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
&& isBluetoothScoOn(); && mDeviceBroker.isBluetoothScoOn();
// Ask audio policy engine to force use Bluetooth SCO channel if needed // Ask audio policy engine to force use Bluetooth SCO channel if needed
final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid() final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
+ "/" + Binder.getCallingPid(); + "/" + Binder.getCallingPid();
@@ -4274,10 +4277,10 @@ public class AudioService extends IAudioService.Stub
// for logging only // for logging only
final int uid = Binder.getCallingUid(); final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid(); final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
.append(") from u/pid:").append(uid).append("/") .append(") from u/pid:").append(uid).append("/")
.append(pid).toString(); .append(pid).toString();
final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+ MediaMetrics.SEPARATOR + "setSpeakerphoneOn") + MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
.setUid(uid) .setUid(uid)
@@ -4285,17 +4288,9 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.STATE, on .set(MediaMetrics.Property.STATE, on
? MediaMetrics.Value.ON : MediaMetrics.Value.OFF) ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
.record(); .record();
final long ident = Binder.clearCallingIdentity();
if (stateChanged) { mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
final long ident = Binder.clearCallingIdentity(); Binder.restoreCallingIdentity(ident);
try {
mContext.sendBroadcastAsUser(
new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
} }
/** @see AudioManager#isSpeakerphoneOn() */ /** @see AudioManager#isSpeakerphoneOn() */
@@ -4303,6 +4298,11 @@ public class AudioService extends IAudioService.Stub
return mDeviceBroker.isSpeakerphoneOn(); return mDeviceBroker.isSpeakerphoneOn();
} }
/** BT SCO audio state seen by apps using the deprecated API setBluetoothScoOn().
* @see isBluetoothScoOn() */
private boolean mBtScoOnByApp;
/** @see AudioManager#setBluetoothScoOn(boolean) */ /** @see AudioManager#setBluetoothScoOn(boolean) */
public void setBluetoothScoOn(boolean on) { public void setBluetoothScoOn(boolean on) {
if (!checkAudioSettingsPermission("setBluetoothScoOn()")) { if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
@@ -4311,7 +4311,7 @@ public class AudioService extends IAudioService.Stub
// Only enable calls from system components // Only enable calls from system components
if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) { if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
mDeviceBroker.setBluetoothScoOnByApp(on); mBtScoOnByApp = on;
return; return;
} }
@@ -4337,7 +4337,7 @@ public class AudioService extends IAudioService.Stub
* Note that it doesn't report internal state, but state seen by apps (which may have * Note that it doesn't report internal state, but state seen by apps (which may have
* called setBluetoothScoOn() */ * called setBluetoothScoOn() */
public boolean isBluetoothScoOn() { public boolean isBluetoothScoOn() {
return mDeviceBroker.isBluetoothScoOnForApp(); return mBtScoOnByApp || mDeviceBroker.isBluetoothScoOn();
} }
// TODO investigate internal users due to deprecation of SDK API // TODO investigate internal users due to deprecation of SDK API
@@ -4384,7 +4384,7 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.SCO_AUDIO_MODE, .set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(scoAudioMode)) BtHelper.scoAudioModeToString(scoAudioMode))
.record(); .record();
startBluetoothScoInt(cb, scoAudioMode, eventSource); startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);
} }
@@ -4403,10 +4403,10 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.SCO_AUDIO_MODE, .set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL)) BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
.record(); .record();
startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
} }
void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) { void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH) MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.set(MediaMetrics.Property.EVENT, "startBluetoothScoInt") .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
.set(MediaMetrics.Property.SCO_AUDIO_MODE, .set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -4417,9 +4417,9 @@ public class AudioService extends IAudioService.Stub
mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record(); mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
return; return;
} }
synchronized (mDeviceBroker.mSetModeLock) { final long ident = Binder.clearCallingIdentity();
mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource); mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
} Binder.restoreCallingIdentity(ident);
mmi.record(); mmi.record();
} }
@@ -4434,9 +4434,9 @@ public class AudioService extends IAudioService.Stub
final String eventSource = new StringBuilder("stopBluetoothSco()") final String eventSource = new StringBuilder("stopBluetoothSco()")
.append(") from u/pid:").append(uid).append("/") .append(") from u/pid:").append(uid).append("/")
.append(pid).toString(); .append(pid).toString();
synchronized (mDeviceBroker.mSetModeLock) { final long ident = Binder.clearCallingIdentity();
mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource); mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
} Binder.restoreCallingIdentity(ident);
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH) new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.setUid(uid) .setUid(uid)
.setPid(pid) .setPid(pid)
@@ -7670,6 +7670,7 @@ public class AudioService extends IAudioService.Stub
pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mHasVibrator="); pw.println(mHasVibrator);
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
pw.print(" mBtScoOnByApp="); pw.println(mBtScoOnByApp);
pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume); pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume);
pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume); pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume);
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices)); pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));

View File

@@ -30,8 +30,6 @@ import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
@@ -39,9 +37,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -58,10 +54,6 @@ public class BtHelper {
mDeviceBroker = broker; mDeviceBroker = broker;
} }
// List of clients having issued a SCO start request
@GuardedBy("BtHelper.this")
private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
// BluetoothHeadset API to control SCO connection // BluetoothHeadset API to control SCO connection
private @Nullable BluetoothHeadset mBluetoothHeadset; private @Nullable BluetoothHeadset mBluetoothHeadset;
@@ -301,6 +293,8 @@ public class BtHelper {
@GuardedBy("AudioDeviceBroker.mDeviceStateLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void receiveBtEvent(Intent intent) { /*package*/ synchronized void receiveBtEvent(Intent intent) {
final String action = intent.getAction(); final String action = intent.getAction();
Log.i(TAG, "receiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
setBtScoActiveDevice(btDevice); setBtScoActiveDevice(btDevice);
@@ -308,20 +302,16 @@ public class BtHelper {
boolean broadcast = false; boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
// broadcast intent if the connection was initated by AudioService Log.i(TAG, "receiveBtEvent ACTION_AUDIO_STATE_CHANGED: " + btState);
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) { switch (btState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED: case BluetoothHeadset.STATE_AUDIO_CONNECTED:
scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
&& mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; 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"); mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
break; break;
@@ -333,21 +323,21 @@ public class BtHelper {
// notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
// 2) If audio was connected then disconnected via Bluetooth APIs and // 2) If audio was connected then disconnected via Bluetooth APIs and
// we still have pending activation requests by apps: this is indicated by // we still have pending activation requests by apps: this is indicated by
// state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty. // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
if (mScoAudioState == SCO_STATE_ACTIVATE_REQ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
|| (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
&& !mScoClients.isEmpty())) { && mDeviceBroker.isBluetoothScoRequested())) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
&& connectBluetoothScoAudioHelper(mBluetoothHeadset, && connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) { mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcast = false; scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
broadcast = true;
break; break;
} }
} }
// Tear down SCO if disconnected from external if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
if (mScoAudioState == SCO_STATE_DEACTIVATING) { broadcast = true;
clearAllScoClients(0, false);
} }
mScoAudioState = SCO_STATE_INACTIVE; mScoAudioState = SCO_STATE_INACTIVE;
break; break;
@@ -356,11 +346,8 @@ public class BtHelper {
&& mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
} }
broadcast = false;
break; break;
default: default:
// do not broadcast CONNECTING or invalid state
broadcast = false;
break; break;
} }
if (broadcast) { if (broadcast) {
@@ -386,81 +373,19 @@ public class BtHelper {
== BluetoothHeadset.STATE_AUDIO_CONNECTED; == BluetoothHeadset.STATE_AUDIO_CONNECTED;
} }
/**
* Disconnect all SCO connections started by {@link AudioManager} except those started by
* {@param exceptPid}
*
* @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
*/
// @GuardedBy("AudioDeviceBroker.mSetModeLock") // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void disconnectBluetoothSco(int exceptPid) { /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
checkScoAudioState();
if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
return;
}
clearAllScoClients(exceptPid, true);
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
@NonNull String eventSource) { @NonNull String eventSource) {
ScoClient client = getScoClient(cb, true);
// The calling identity must be cleared before calling ScoClient.incCount().
// inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
// and this must be done on behalf of system server to make sure permissions are granted.
// The caller identity must be cleared after getScoClient() because it is needed if a new
// client is created.
final long ident = Binder.clearCallingIdentity();
try {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
} catch (NullPointerException e) {
Log.e(TAG, "Null ScoClient", e);
}
Binder.restoreCallingIdentity(ident);
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*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
// and this must be done on behalf of system server to make sure permissions are granted.
final long ident = Binder.clearCallingIdentity();
if (client != null) {
stopAndRemoveClient(client, eventSource);
}
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")
// @GuardedBy("BtHelper.this")
private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
SCO_MODE_VIRTUAL_CALL); }
// If a disconnection is pending, the client will be removed when clearAllScoClients()
// is called form receiveBtEvent() // @GuardedBy("AudioDeviceBroker.mSetModeLock")
if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
&& mScoAudioState != SCO_STATE_DEACTIVATING) { /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
client.remove(false /*stop */, true /*unregister*/); AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
} return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
} }
/*package*/ synchronized void setHearingAidVolume(int index, int streamType) { /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
@@ -507,7 +432,6 @@ public class BtHelper {
// @GuardedBy("AudioDeviceBroker.mSetModeLock") // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void resetBluetoothSco() { /*package*/ synchronized void resetBluetoothSco() {
clearAllScoClients(0, false);
mScoAudioState = SCO_STATE_INACTIVE; mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
AudioSystem.setParameters("A2dpSuspended=false"); AudioSystem.setParameters("A2dpSuspended=false");
@@ -733,195 +657,122 @@ public class BtHelper {
}; };
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// @GuardedBy("AudioDeviceBroker.mSetModeLock") // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock") //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void scoClientDied(Object obj) { @GuardedBy("BtHelper.this")
final ScoClient client = (ScoClient) obj; private boolean requestScoState(int state, int scoAudioMode) {
client.remove(true /*stop*/, false /*unregister*/); checkScoAudioState();
Log.w(TAG, "SCO client died"); if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
} // Make sure that the state transitions to CONNECTING even if we cannot initiate
// the connection.
private class ScoClient implements IBinder.DeathRecipient { broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
private IBinder mCb; // To be notified of client's death switch (mScoAudioState) {
private int mCreatorPid; case SCO_STATE_INACTIVE:
mScoAudioMode = scoAudioMode;
ScoClient(IBinder cb) { if (scoAudioMode == SCO_MODE_UNDEFINED) {
mCb = cb; mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
mCreatorPid = Binder.getCallingPid(); if (mBluetoothHeadsetDevice != null) {
} mScoAudioMode = Settings.Global.getInt(
mDeviceBroker.getContentResolver(),
public void registerDeathRecipient() { "bluetooth_sco_channel_"
try { + mBluetoothHeadsetDevice.getAddress(),
mCb.linkToDeath(this, 0); SCO_MODE_VIRTUAL_CALL);
} catch (RemoteException e) { if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
Log.w(TAG, "ScoClient could not link to " + mCb + " binder death"); mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
} }
} }
}
public void unregisterDeathRecipient() { if (mBluetoothHeadset == null) {
try { if (getBluetoothHeadset()) {
mCb.unlinkToDeath(this, 0); mScoAudioState = SCO_STATE_ACTIVATE_REQ;
} catch (NoSuchElementException e) { } else {
Log.w(TAG, "ScoClient could not not unregistered to binder"); Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
} + " connection, mScoAudioMode=" + mScoAudioMode);
} broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@Override return false;
public void binderDied() { }
// process this from DeviceBroker's message queue to take the right locks since break;
// this event can impact SCO mode and requires querying audio mode stack }
mDeviceBroker.postScoClientDied(this); if (mBluetoothHeadsetDevice == null) {
} Log.w(TAG, "requestScoState: no active device while connecting,"
+ " mScoAudioMode=" + mScoAudioMode);
IBinder getBinder() { broadcastScoConnectionState(
return mCb; AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
} return false;
}
int getPid() { if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
return mCreatorPid; mBluetoothHeadsetDevice, mScoAudioMode)) {
} mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} else {
// @GuardedBy("AudioDeviceBroker.mSetModeLock") Log.w(TAG, "requestScoState: connect to "
//@GuardedBy("AudioDeviceBroker.mDeviceStateLock") + getAnonymizedAddress(mBluetoothHeadsetDevice)
@GuardedBy("BtHelper.this") + " failed, mScoAudioMode=" + mScoAudioMode);
private boolean requestScoState(int state, int scoAudioMode) { broadcastScoConnectionState(
checkScoAudioState(); AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
if (mScoClients.size() != 1) { return false;
Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode }
+ ", num SCO clients=" + mScoClients.size()); break;
return true; case SCO_STATE_DEACTIVATING:
} mScoAudioState = SCO_STATE_ACTIVATE_REQ;
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { break;
// Make sure that the state transitions to CONNECTING even if we cannot initiate case SCO_STATE_DEACTIVATE_REQ:
// the connection. mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
// Accept SCO audio activation only in NORMAL audio mode or if the mode is break;
// currently controlled by the same client process. case SCO_STATE_ACTIVE_INTERNAL:
final int modeOwnerPid = mDeviceBroker.getModeOwnerPid(); Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { break;
Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " default:
+ modeOwnerPid + " != creatorPid " + mCreatorPid); Log.w(TAG, "requestScoState: failed to connect in state "
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false; return false;
} }
switch (mScoAudioState) { } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
case SCO_STATE_INACTIVE: switch (mScoAudioState) {
mScoAudioMode = scoAudioMode; case SCO_STATE_ACTIVE_INTERNAL:
if (scoAudioMode == SCO_MODE_UNDEFINED) { if (mBluetoothHeadset == null) {
mScoAudioMode = SCO_MODE_VIRTUAL_CALL; if (getBluetoothHeadset()) {
if (mBluetoothHeadsetDevice != null) { mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
mScoAudioMode = Settings.Global.getInt(
mDeviceBroker.getContentResolver(),
"bluetooth_sco_channel_"
+ mBluetoothHeadsetDevice.getAddress(),
SCO_MODE_VIRTUAL_CALL);
if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
}
}
}
if (mBluetoothHeadset == null) {
if (getBluetoothHeadset()) {
mScoAudioState = SCO_STATE_ACTIVATE_REQ;
} else {
Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ " connection, mScoAudioMode=" + mScoAudioMode);
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
}
break;
}
if (mBluetoothHeadsetDevice == null) {
Log.w(TAG, "requestScoState: no active device while connecting,"
+ " mScoAudioMode=" + mScoAudioMode);
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
}
if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} else { } else {
Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ " failed, mScoAudioMode=" + mScoAudioMode); + " disconnection, mScoAudioMode=" + mScoAudioMode);
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState( broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED); AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false; return false;
} }
break; break;
case SCO_STATE_DEACTIVATING: }
mScoAudioState = SCO_STATE_ACTIVATE_REQ; if (mBluetoothHeadsetDevice == null) {
break;
case SCO_STATE_DEACTIVATE_REQ:
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
break;
case SCO_STATE_ACTIVE_INTERNAL:
Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
break;
default:
Log.w(TAG, "requestScoState: failed to connect in state "
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
}
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
switch (mScoAudioState) {
case SCO_STATE_ACTIVE_INTERNAL:
if (mBluetoothHeadset == null) {
if (getBluetoothHeadset()) {
mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
} else {
Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ " disconnection, mScoAudioMode=" + mScoAudioMode);
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
}
break;
}
if (mBluetoothHeadsetDevice == null) {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
break;
}
if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_DEACTIVATING;
} else {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
break;
case SCO_STATE_ACTIVATE_REQ:
mScoAudioState = SCO_STATE_INACTIVE; mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
break; break;
default: }
Log.w(TAG, "requestScoState: failed to disconnect in state " if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode); mBluetoothHeadsetDevice, mScoAudioMode)) {
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); mScoAudioState = SCO_STATE_DEACTIVATING;
return false; } else {
} mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
break;
case SCO_STATE_ACTIVATE_REQ:
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
break;
default:
Log.w(TAG, "requestScoState: failed to disconnect in state "
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
} }
return true;
}
@GuardedBy("BtHelper.this")
void remove(boolean stop, boolean unregister) {
if (unregister) {
unregisterDeathRecipient();
}
if (stop) {
requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
SCO_MODE_VIRTUAL_CALL);
}
mScoClients.remove(this);
} }
return true;
} }
//----------------------------------------------------- //-----------------------------------------------------
@@ -974,49 +825,6 @@ public class BtHelper {
} }
} }
@GuardedBy("BtHelper.this")
private ScoClient getScoClient(IBinder cb, boolean create) {
for (ScoClient existingClient : mScoClients) {
if (existingClient.getBinder() == cb) {
return existingClient;
}
}
if (create) {
ScoClient newClient = new ScoClient(cb);
newClient.registerDeathRecipient();
mScoClients.add(newClient);
return newClient;
}
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.mDeviceStateLock")
@GuardedBy("BtHelper.this")
private void clearAllScoClients(int exceptPid, boolean stopSco) {
final ArrayList<ScoClient> clients = new ArrayList<ScoClient>();
for (ScoClient cl : mScoClients) {
if (cl.getPid() != exceptPid) {
clients.add(cl);
}
}
for (ScoClient cl : clients) {
cl.remove(stopSco, true /*unregister*/);
}
}
private boolean getBluetoothHeadset() { private boolean getBluetoothHeadset() {
boolean result = false; boolean result = false;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1062,10 +870,6 @@ public class BtHelper {
pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice); pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState)); pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode)); pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
pw.println(prefix + "Sco clients:");
mScoClients.forEach((cl) -> {
pw.println(" " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); });
pw.println("\n" + prefix + "mHearingAid: " + mHearingAid); pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
pw.println(prefix + "mA2dp: " + mA2dp); pw.println(prefix + "mA2dp: " + mA2dp);
pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported); pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);