AudioService: refactor communication route control
Audio routing for communication use cases (cell, video and VoIP calls) is controlled by separate APIs: setSpeakerPhoneOn(), startBluetoothSco() and stopBluetoothSco(). Requests for speakerphone and bluetooth are managed by separate client stacks although in the end they control a single routing state in audio policy manager, which creates problems in case of conflicting requests and race conditions. This CL refactors the implementation by regrouping the Bluetooth Sco and Speaker client stacks into a single one. This makes sure that no conflicting routing request exist for a given client. It also makes handling of race conditions and prioritization between clients easier. Finally, it prepares the introduction of a new API that will be the single entry point for communication route control. Also in this CL: - Option to enable more debug log for communication route - Fixes in BtHelper.receiveBtEvent() 1) Fix intent broadcast missing in case of transition from external activation to internal activation. 2) Do not clear SCO requests when SCO audio is disconnected: this can happen because current active communication route request is different from SCO but pending requests must stay in the stack. 3) Do not clear SCO requests when the audio mode owner changes. Any Active communication route requested by new audio mode owner will be honored and pending requests will be restored when mode owner changes again. Test: regression tests with cell and VoIP calls Change-Id: If9d2f24b9def78ccd791294ed42d95ce30f0ba8b
This commit is contained in:
@@ -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>();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -3765,7 +3768,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();
|
||||||
@@ -4406,10 +4409,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)
|
||||||
@@ -4417,17 +4420,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() */
|
||||||
@@ -4435,6 +4430,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()")) {
|
||||||
@@ -4443,7 +4443,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4469,7 +4469,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
|
||||||
@@ -4516,7 +4516,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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4535,10 +4535,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,
|
||||||
@@ -4549,9 +4549,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4566,9 +4566,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)
|
||||||
@@ -7848,6 +7848,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));
|
||||||
|
|||||||
@@ -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");
|
||||||
@@ -740,196 +664,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 "
|
Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
|
||||||
+ getAnonymizedAddress(mBluetoothHeadsetDevice)
|
+ " disconnection, mScoAudioMode=" + mScoAudioMode);
|
||||||
+ " failed, 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
@@ -982,49 +832,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();
|
||||||
@@ -1070,10 +877,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);
|
||||||
|
|||||||
Reference in New Issue
Block a user