Merge "Bluetooth: Fix HFP SCO logic and documentation" into pi-dev am: 1ae21127da

am: e3e55b865b

Change-Id: Ic45224f318646958574fd624ee228c8938e1a814
This commit is contained in:
android-build-team Robot
2018-05-07 13:03:22 -07:00
committed by android-build-merger
4 changed files with 331 additions and 219 deletions

View File

@@ -559,8 +559,8 @@ Landroid/bluetooth/BluetoothHeadset;->connectAudio()Z
Landroid/bluetooth/BluetoothHeadset;->disconnectAudio()Z
Landroid/bluetooth/BluetoothHeadset;->getActiveDevice()Landroid/bluetooth/BluetoothDevice;
Landroid/bluetooth/BluetoothHeadset;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z
Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall()Z
Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall()Z
Landroid/bluetooth/BluetoothHearingAid;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String;
Landroid/bluetooth/BluetoothHearingAid;->getActiveDevices()Ljava/util/List;
Landroid/bluetooth/BluetoothHearingAid;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z

View File

@@ -16,6 +16,7 @@
package android.bluetooth;
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -633,8 +634,9 @@ public final class BluetoothHeadset implements BluetoothProfile {
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param device Bluetooth headset
* @return false if there is no headset connected of if the connected headset doesn't support
* voice recognition or on error, true otherwise
* @return false if there is no headset connected, or the connected headset doesn't support
* voice recognition, or voice recognition is already started, or audio channel is occupied,
* or on error, true otherwise
*/
public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()");
@@ -654,10 +656,15 @@ public final class BluetoothHeadset implements BluetoothProfile {
* Stop Bluetooth Voice Recognition mode, and shut down the
* Bluetooth audio path.
*
* <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* If this function returns true, this intent will be broadcasted with
* {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param device Bluetooth headset
* @return false if there is no headset connected or on error, true otherwise
* @return false if there is no headset connected, or voice recognition has not started,
* or voice recognition has ended on this headset, or on error, true otherwise
*/
public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()");
@@ -798,11 +805,12 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
* Check if Bluetooth SCO audio is connected.
* Check if at least one headset's SCO audio is connected or connecting
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @return true if SCO is connected, false otherwise or on error
* @return true if at least one device's SCO audio is connected or connecting, false otherwise
* or on error
* @hide
*/
public boolean isAudioOn() {
@@ -821,11 +829,21 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
* Initiates a connection of headset audio.
* It setup SCO channel with remote connected headset device.
* Initiates a connection of headset audio to the current active device
*
* @return true if successful false if there was some error such as there is no connected
* headset
* <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* If this function returns true, this intent will be broadcasted with
* {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
*
* <p> {@link #EXTRA_STATE} will transition from
* {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
* audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
* in case of failure to establish the audio connection.
*
* Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
* before calling this method
*
* @return false if there was some error such as there is no active headset
* @hide
*/
public boolean connectAudio() {
@@ -844,11 +862,14 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
* Initiates a disconnection of headset audio.
* It tears down the SCO channel from remote headset device.
* Initiates a disconnection of HFP SCO audio.
* Tear down voice recognition or virtual voice call if any.
*
* @return true if successful false if there was some error such as there is no connected SCO
* channel
* <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* If this function returns true, this intent will be broadcasted with
* {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
*
* @return false if audio is not connected, or on error, true otherwise
* @hide
*/
public boolean disconnectAudio() {
@@ -867,22 +888,33 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
* Initiates a SCO channel connection with the headset (if connected).
* Also initiates a virtual voice call for Handsfree devices as many devices
* do not accept SCO audio without a call.
* This API allows the handsfree device to be used for routing non-cellular
* call audio.
* Initiates a SCO channel connection as a virtual voice call to the current active device
* Active handsfree device will be notified of incoming call and connected call.
*
* @param device Remote Bluetooth Device
* @return true if successful, false if there was some error.
* <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* If this function returns true, this intent will be broadcasted with
* {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
*
* <p> {@link #EXTRA_STATE} will transition from
* {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
* audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
* in case of failure to establish the audio connection.
*
* @return true if successful, false if one of the following case applies
* - SCO audio is not idle (connecting or connected)
* - virtual call has already started
* - there is no active device
* - a Telecom managed call is going on
* - binder is dead or Bluetooth is disabled or other error
* @hide
*/
public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean startScoUsingVirtualVoiceCall() {
if (DBG) log("startScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
if (service != null && isEnabled()) {
try {
return service.startScoUsingVirtualVoiceCall(device);
return service.startScoUsingVirtualVoiceCall();
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
@@ -894,19 +926,24 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
* Terminates an ongoing SCO connection and the associated virtual
* call.
* Terminates an ongoing SCO connection and the associated virtual call.
*
* @param device Remote Bluetooth Device
* @return true if successful, false if there was some error.
* <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* If this function returns true, this intent will be broadcasted with
* {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
*
* @return true if successful, false if one of the following case applies
* - virtual voice call is not started or has ended
* - binder is dead or Bluetooth is disabled or other error
* @hide
*/
public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean stopScoUsingVirtualVoiceCall() {
if (DBG) log("stopScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
if (service != null && isEnabled()) {
try {
return service.stopScoUsingVirtualVoiceCall(device);
return service.stopScoUsingVirtualVoiceCall();
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}

View File

@@ -4559,8 +4559,7 @@ public class AudioManager {
/**
* The list of {@link AudioDeviceCallback} objects to receive add/remove notifications.
*/
private ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>
mDeviceCallbacks =
private final ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> mDeviceCallbacks =
new ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>();
/**
@@ -4860,22 +4859,21 @@ public class AudioManager {
calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL);
AudioDeviceInfo[] removed_devices =
calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL);
if (added_devices.length != 0 || removed_devices.length != 0) {
synchronized (mDeviceCallbacks) {
for (int i = 0; i < mDeviceCallbacks.size(); i++) {
handler = mDeviceCallbacks.valueAt(i).getHandler();
if (handler != null) {
if (added_devices.length != 0) {
handler.sendMessage(Message.obtain(handler,
MSG_DEVICES_DEVICES_ADDED,
added_devices));
}
if (removed_devices.length != 0) {
handler.sendMessage(Message.obtain(handler,
MSG_DEVICES_DEVICES_REMOVED,
removed_devices));
}
if (added_devices.length != 0) {
handler.sendMessage(Message.obtain(handler,
MSG_DEVICES_DEVICES_ADDED,
added_devices));
}
}
}
}

View File

@@ -525,6 +525,8 @@ public class AudioService extends IAudioService.Stub
private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
// SCO audio deactivation request waiting for headset service to connect
private static final int SCO_STATE_DEACTIVATE_REQ = 5;
// SCO audio deactivation in progress, waiting for Bluetooth audio intent
private static final int SCO_STATE_DEACTIVATING = 6;
// SCO audio state is active due to an action in BT handsfree (either voice recognition or
// in call audio)
@@ -2718,9 +2720,13 @@ public class AudioService extends IAudioService.Stub
}
public void binderDied() {
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
synchronized(mSetModeDeathHandlers) {
Log.w(TAG, "setMode() client died");
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
int index = mSetModeDeathHandlers.indexOf(this);
if (index < 0) {
Log.w(TAG, "unregistered setMode() client died");
@@ -2729,8 +2735,8 @@ public class AudioService extends IAudioService.Stub
}
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode
if (newModeOwnerPid != 0) {
// SCO connections not started by the application changing the mode when pid changes
if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
final long ident = Binder.clearCallingIdentity();
disconnectBluetoothSco(newModeOwnerPid);
Binder.restoreCallingIdentity(ident);
@@ -2774,17 +2780,21 @@ public class AudioService extends IAudioService.Stub
return;
}
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
synchronized(mSetModeDeathHandlers) {
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
if (mode == AudioSystem.MODE_CURRENT) {
mode = mMode;
}
newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode
if (newModeOwnerPid != 0) {
disconnectBluetoothSco(newModeOwnerPid);
// SCO connections not started by the application changing the mode when pid changes
if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
disconnectBluetoothSco(newModeOwnerPid);
}
}
@@ -3200,28 +3210,17 @@ public class AudioService extends IAudioService.Stub
}
public void setBluetoothScoOnInt(boolean on, String eventSource) {
if (DEBUG_DEVICES) {
Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
}
Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
if (on) {
// do not accept SCO ON if SCO audio is not connected
synchronized (mScoClients) {
if (mBluetoothHeadset != null) {
if (mBluetoothHeadsetDevice == null) {
BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice();
if (activeDevice != null) {
// setBtScoActiveDevice() might trigger resetBluetoothSco() which
// will call setBluetoothScoOnInt(false, "resetBluetoothSco")
setBtScoActiveDevice(activeDevice);
}
}
if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_CONNECTED) {
mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+ mBluetoothHeadsetDevice + " is not in audio connected mode");
return;
}
if ((mBluetoothHeadset != null)
&& (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+ mBluetoothHeadsetDevice + " is not in audio connected mode");
return;
}
}
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3401,9 +3400,8 @@ public class AudioService extends IAudioService.Stub
public int totalCount() {
synchronized(mScoClients) {
int count = 0;
int size = mScoClients.size();
for (int i = 0; i < size; i++) {
count += mScoClients.get(i).getCount();
for (ScoClient mScoClient : mScoClients) {
count += mScoClient.getCount();
}
return count;
}
@@ -3411,128 +3409,161 @@ public class AudioService extends IAudioService.Stub
private void requestScoState(int state, int scoAudioMode) {
checkScoAudioState();
if (totalCount() == 0) {
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// Make sure that the state transitions to CONNECTING even if we cannot initiate
// the connection.
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
// Accept SCO audio activation only in NORMAL audio mode or if the mode is
// currently controlled by the same client process.
synchronized(mSetModeDeathHandlers) {
if ((mSetModeDeathHandlers.isEmpty() ||
mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
(mScoAudioState == SCO_STATE_INACTIVE ||
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_INACTIVE) {
mScoAudioMode = scoAudioMode;
if (scoAudioMode == SCO_MODE_UNDEFINED) {
if (mBluetoothHeadsetDevice != null) {
mScoAudioMode = new Integer(Settings.Global.getInt(
mContentResolver,
"bluetooth_sco_channel_"+
mBluetoothHeadsetDevice.getAddress(),
SCO_MODE_VIRTUAL_CALL));
if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
}
} else {
mScoAudioMode = SCO_MODE_RAW;
}
}
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
boolean status = false;
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.connectAudio();
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
} else if (mScoAudioMode == SCO_MODE_VR) {
status = mBluetoothHeadset.startVoiceRecognition(
mBluetoothHeadsetDevice);
}
if (status) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} else {
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
} else if (getBluetoothHeadset()) {
mScoAudioState = SCO_STATE_ACTIVATE_REQ;
}
} else {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
}
} else {
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
int clientCount = totalCount();
if (clientCount != 0) {
Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+ ", clientCount=" + clientCount);
return;
}
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// Make sure that the state transitions to CONNECTING even if we cannot initiate
// the connection.
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
// Accept SCO audio activation only in NORMAL audio mode or if the mode is
// currently controlled by the same client process.
synchronized(mSetModeDeathHandlers) {
int modeOwnerPid = mSetModeDeathHandlers.isEmpty()
? 0 : mSetModeDeathHandlers.get(0).getPid();
if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+ modeOwnerPid + " != creatorPid " + mCreatorPid);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return;
}
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
(mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
boolean status = false;
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.disconnectAudio();
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
} else if (mScoAudioMode == SCO_MODE_VR) {
status = mBluetoothHeadset.stopVoiceRecognition(
mBluetoothHeadsetDevice);
switch (mScoAudioState) {
case SCO_STATE_INACTIVE:
mScoAudioMode = scoAudioMode;
if (scoAudioMode == SCO_MODE_UNDEFINED) {
mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
if (mBluetoothHeadsetDevice != null) {
mScoAudioMode = Settings.Global.getInt(mContentResolver,
"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);
}
break;
}
if (mBluetoothHeadsetDevice == null) {
Log.w(TAG, "requestScoState: no active device while connecting,"
+ " mScoAudioMode=" + mScoAudioMode);
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
break;
}
if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} else {
Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+ " failed, mScoAudioMode=" + mScoAudioMode);
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
break;
case SCO_STATE_DEACTIVATING:
mScoAudioState = SCO_STATE_ACTIVATE_REQ;
break;
case SCO_STATE_DEACTIVATE_REQ:
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
break;
default:
Log.w(TAG, "requestScoState: failed to connect in state "
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
break;
if (!status) {
}
}
} 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);
}
} else if (getBluetoothHeadset()) {
mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
break;
}
} else {
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;
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);
break;
}
}
}
}
private void checkScoAudioState() {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
mScoAudioState == SCO_STATE_INACTIVE &&
mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
synchronized (mScoClients) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
mScoAudioState == SCO_STATE_INACTIVE &&
mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
}
}
private ScoClient getScoClient(IBinder cb, boolean create) {
synchronized(mScoClients) {
ScoClient client = null;
int size = mScoClients.size();
for (int i = 0; i < size; i++) {
client = mScoClients.get(i);
if (client.getBinder() == cb)
return client;
for (ScoClient existingClient : mScoClients) {
if (existingClient.getBinder() == cb) {
return existingClient;
}
}
if (create) {
client = new ScoClient(cb);
mScoClients.add(client);
ScoClient newClient = new ScoClient(cb);
mScoClients.add(newClient);
return newClient;
}
return client;
return null;
}
}
public void clearAllScoClients(int exceptPid, boolean stopSco) {
synchronized(mScoClients) {
ScoClient savedClient = null;
int size = mScoClients.size();
for (int i = 0; i < size; i++) {
ScoClient cl = mScoClients.get(i);
for (ScoClient cl : mScoClients) {
if (cl.getPid() != exceptPid) {
cl.clearCount(stopSco);
} else {
@@ -3569,10 +3600,17 @@ public class AudioService extends IAudioService.Stub
mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
if (mBluetoothHeadsetDevice != null) {
if (mBluetoothHeadset != null) {
if (!mBluetoothHeadset.stopVoiceRecognition(
mBluetoothHeadsetDevice)) {
sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
SENDMSG_REPLACE, 0, 0, null, 0);
boolean status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, SCO_MODE_RAW)
|| disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL)
|| disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, SCO_MODE_VR);
if (status) {
mScoAudioState = SCO_STATE_DEACTIVATING;
} else {
clearAllScoClients(exceptPid, false);
mScoAudioState = SCO_STATE_INACTIVE;
}
} else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
getBluetoothHeadset()) {
@@ -3585,6 +3623,34 @@ public class AudioService extends IAudioService.Stub
}
}
private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
BluetoothDevice device, int scoAudioMode) {
switch (scoAudioMode) {
case SCO_MODE_RAW:
return bluetoothHeadset.disconnectAudio();
case SCO_MODE_VIRTUAL_CALL:
return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
case SCO_MODE_VR:
return bluetoothHeadset.stopVoiceRecognition(device);
default:
return false;
}
}
private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
BluetoothDevice device, int scoAudioMode) {
switch (scoAudioMode) {
case SCO_MODE_RAW:
return bluetoothHeadset.connectAudio();
case SCO_MODE_VIRTUAL_CALL:
return bluetoothHeadset.startScoUsingVirtualVoiceCall();
case SCO_MODE_VR:
return bluetoothHeadset.startVoiceRecognition(device);
default:
return false;
}
}
private void resetBluetoothSco() {
synchronized(mScoClients) {
clearAllScoClients(0, false);
@@ -3642,11 +3708,9 @@ public class AudioService extends IAudioService.Stub
return result;
}
void setBtScoActiveDevice(BluetoothDevice btDevice) {
if (DEBUG_DEVICES) {
Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
}
private void setBtScoActiveDevice(BluetoothDevice btDevice) {
synchronized (mScoClients) {
Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
if (!Objects.equals(btDevice, previousActiveDevice)) {
if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
@@ -3726,37 +3790,36 @@ public class AudioService extends IAudioService.Stub
boolean status = false;
if (mBluetoothHeadsetDevice != null) {
switch (mScoAudioState) {
case SCO_STATE_ACTIVATE_REQ:
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.connectAudio();
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
} else if (mScoAudioMode == SCO_MODE_VR) {
status = mBluetoothHeadset.startVoiceRecognition(
mBluetoothHeadsetDevice);
}
break;
case SCO_STATE_DEACTIVATE_REQ:
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.disconnectAudio();
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
} else if (mScoAudioMode == SCO_MODE_VR) {
status = mBluetoothHeadset.stopVoiceRecognition(
mBluetoothHeadsetDevice);
}
break;
case SCO_STATE_DEACTIVATE_EXT_REQ:
status = mBluetoothHeadset.stopVoiceRecognition(
mBluetoothHeadsetDevice);
case SCO_STATE_ACTIVATE_REQ:
status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode);
if (status) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
}
break;
case SCO_STATE_DEACTIVATE_REQ:
status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode);
if (status) {
mScoAudioState = SCO_STATE_DEACTIVATING;
}
break;
case SCO_STATE_DEACTIVATE_EXT_REQ:
status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, SCO_MODE_RAW) ||
disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL) ||
disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, SCO_MODE_VR);
if (status) {
mScoAudioState = SCO_STATE_DEACTIVATING;
}
break;
}
}
if (!status) {
sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
SENDMSG_REPLACE, 0, 0, null, 0);
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
}
}
@@ -6326,33 +6389,47 @@ public class AudioService extends IAudioService.Stub
if (!mScoClients.isEmpty() &&
(mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
mScoAudioState == SCO_STATE_DEACTIVATING)) {
broadcast = true;
}
switch (btState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
mScoAudioState = SCO_STATE_INACTIVE;
clearAllScoClients(0, false);
break;
case BluetoothHeadset.STATE_AUDIO_CONNECTING:
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
default:
// do not broadcast CONNECTING or invalid state
broadcast = false;
break;
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
setBluetoothScoOn(true);
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
setBluetoothScoOn(false);
scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
// startBluetoothSco called after stopBluetoothSco
if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
&& connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcast = false;
break;
}
}
// Tear down SCO if disconnected from external
clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
mScoAudioState = SCO_STATE_INACTIVE;
break;
case BluetoothHeadset.STATE_AUDIO_CONNECTING:
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
default:
// do not broadcast CONNECTING or invalid state
broadcast = false;
break;
}
}
if (broadcast) {