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

am: 1ae21127da

Change-Id: I9b4fcac73a283aac1794e13cce464728612fd0dc
This commit is contained in:
android-build-team Robot
2018-05-07 12:56:06 -07:00
committed by android-build-merger
4 changed files with 331 additions and 219 deletions

View File

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

View File

@@ -16,6 +16,7 @@
package android.bluetooth; package android.bluetooth;
import android.Manifest;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission; import android.annotation.RequiresPermission;
import android.annotation.SdkConstant; import android.annotation.SdkConstant;
@@ -633,8 +634,9 @@ public final class BluetoothHeadset implements BluetoothProfile {
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
* *
* @param device Bluetooth headset * @param device Bluetooth headset
* @return false if there is no headset connected of if the connected headset doesn't support * @return false if there is no headset connected, or the connected headset doesn't support
* voice recognition or on error, true otherwise * voice recognition, or voice recognition is already started, or audio channel is occupied,
* or on error, true otherwise
*/ */
public boolean startVoiceRecognition(BluetoothDevice device) { public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()"); if (DBG) log("startVoiceRecognition()");
@@ -654,10 +656,15 @@ public final class BluetoothHeadset implements BluetoothProfile {
* Stop Bluetooth Voice Recognition mode, and shut down the * Stop Bluetooth Voice Recognition mode, and shut down the
* Bluetooth audio path. * 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. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
* *
* @param device Bluetooth headset * @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) { public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()"); 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. * <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 * @hide
*/ */
public boolean isAudioOn() { public boolean isAudioOn() {
@@ -821,11 +829,21 @@ public final class BluetoothHeadset implements BluetoothProfile {
} }
/** /**
* Initiates a connection of headset audio. * Initiates a connection of headset audio to the current active device
* It setup SCO channel with remote connected headset device.
* *
* @return true if successful false if there was some error such as there is no connected * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* headset * 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 * @hide
*/ */
public boolean connectAudio() { public boolean connectAudio() {
@@ -844,11 +862,14 @@ public final class BluetoothHeadset implements BluetoothProfile {
} }
/** /**
* Initiates a disconnection of headset audio. * Initiates a disconnection of HFP SCO audio.
* It tears down the SCO channel from remote headset device. * 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 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* channel * 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 * @hide
*/ */
public boolean disconnectAudio() { public boolean disconnectAudio() {
@@ -867,22 +888,33 @@ public final class BluetoothHeadset implements BluetoothProfile {
} }
/** /**
* Initiates a SCO channel connection with the headset (if connected). * Initiates a SCO channel connection as a virtual voice call to the current active device
* Also initiates a virtual voice call for Handsfree devices as many devices * Active handsfree device will be notified of incoming call and connected call.
* do not accept SCO audio without a call.
* This API allows the handsfree device to be used for routing non-cellular
* call audio.
* *
* @param device Remote Bluetooth Device * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* @return true if successful, false if there was some error. * 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 * @hide
*/ */
public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean startScoUsingVirtualVoiceCall() {
if (DBG) log("startScoUsingVirtualVoiceCall()"); if (DBG) log("startScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService; final IBluetoothHeadset service = mService;
if (service != null && isEnabled() && isValidDevice(device)) { if (service != null && isEnabled()) {
try { try {
return service.startScoUsingVirtualVoiceCall(device); return service.startScoUsingVirtualVoiceCall();
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
} }
@@ -894,19 +926,24 @@ public final class BluetoothHeadset implements BluetoothProfile {
} }
/** /**
* Terminates an ongoing SCO connection and the associated virtual * Terminates an ongoing SCO connection and the associated virtual call.
* call.
* *
* @param device Remote Bluetooth Device * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
* @return true if successful, false if there was some error. * 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 * @hide
*/ */
public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean stopScoUsingVirtualVoiceCall() {
if (DBG) log("stopScoUsingVirtualVoiceCall()"); if (DBG) log("stopScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService; final IBluetoothHeadset service = mService;
if (service != null && isEnabled() && isValidDevice(device)) { if (service != null && isEnabled()) {
try { try {
return service.stopScoUsingVirtualVoiceCall(device); return service.stopScoUsingVirtualVoiceCall();
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
} }

View File

@@ -4558,8 +4558,7 @@ public class AudioManager {
/** /**
* The list of {@link AudioDeviceCallback} objects to receive add/remove notifications. * The list of {@link AudioDeviceCallback} objects to receive add/remove notifications.
*/ */
private ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> private final ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> mDeviceCallbacks =
mDeviceCallbacks =
new ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>(); new ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>();
/** /**
@@ -4859,22 +4858,21 @@ public class AudioManager {
calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL); calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL);
AudioDeviceInfo[] removed_devices = AudioDeviceInfo[] removed_devices =
calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL); calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL);
if (added_devices.length != 0 || removed_devices.length != 0) { if (added_devices.length != 0 || removed_devices.length != 0) {
synchronized (mDeviceCallbacks) { synchronized (mDeviceCallbacks) {
for (int i = 0; i < mDeviceCallbacks.size(); i++) { for (int i = 0; i < mDeviceCallbacks.size(); i++) {
handler = mDeviceCallbacks.valueAt(i).getHandler(); handler = mDeviceCallbacks.valueAt(i).getHandler();
if (handler != null) { if (handler != null) {
if (added_devices.length != 0) {
handler.sendMessage(Message.obtain(handler,
MSG_DEVICES_DEVICES_ADDED,
added_devices));
}
if (removed_devices.length != 0) { if (removed_devices.length != 0) {
handler.sendMessage(Message.obtain(handler, handler.sendMessage(Message.obtain(handler,
MSG_DEVICES_DEVICES_REMOVED, MSG_DEVICES_DEVICES_REMOVED,
removed_devices)); 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; private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
// SCO audio deactivation request waiting for headset service to connect // SCO audio deactivation request waiting for headset service to connect
private static final int SCO_STATE_DEACTIVATE_REQ = 5; 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 // SCO audio state is active due to an action in BT handsfree (either voice recognition or
// in call audio) // in call audio)
@@ -2714,9 +2716,13 @@ public class AudioService extends IAudioService.Stub
} }
public void binderDied() { public void binderDied() {
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0; int newModeOwnerPid = 0;
synchronized(mSetModeDeathHandlers) { synchronized(mSetModeDeathHandlers) {
Log.w(TAG, "setMode() client died"); Log.w(TAG, "setMode() client died");
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
int index = mSetModeDeathHandlers.indexOf(this); int index = mSetModeDeathHandlers.indexOf(this);
if (index < 0) { if (index < 0) {
Log.w(TAG, "unregistered setMode() client died"); Log.w(TAG, "unregistered setMode() client died");
@@ -2725,8 +2731,8 @@ public class AudioService extends IAudioService.Stub
} }
} }
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode // SCO connections not started by the application changing the mode when pid changes
if (newModeOwnerPid != 0) { if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
disconnectBluetoothSco(newModeOwnerPid); disconnectBluetoothSco(newModeOwnerPid);
Binder.restoreCallingIdentity(ident); Binder.restoreCallingIdentity(ident);
@@ -2770,17 +2776,21 @@ public class AudioService extends IAudioService.Stub
return; return;
} }
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0; int newModeOwnerPid = 0;
synchronized(mSetModeDeathHandlers) { synchronized(mSetModeDeathHandlers) {
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
if (mode == AudioSystem.MODE_CURRENT) { if (mode == AudioSystem.MODE_CURRENT) {
mode = mMode; mode = mMode;
} }
newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
} }
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode // SCO connections not started by the application changing the mode when pid changes
if (newModeOwnerPid != 0) { if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
disconnectBluetoothSco(newModeOwnerPid); disconnectBluetoothSco(newModeOwnerPid);
} }
} }
@@ -3196,28 +3206,17 @@ public class AudioService extends IAudioService.Stub
} }
public void setBluetoothScoOnInt(boolean on, String eventSource) { public void setBluetoothScoOnInt(boolean on, String eventSource) {
if (DEBUG_DEVICES) { Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
}
if (on) { if (on) {
// do not accept SCO ON if SCO audio is not connected // do not accept SCO ON if SCO audio is not connected
synchronized (mScoClients) { synchronized (mScoClients) {
if (mBluetoothHeadset != null) { if ((mBluetoothHeadset != null)
if (mBluetoothHeadsetDevice == null) { && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice(); != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
if (activeDevice != null) { mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
// setBtScoActiveDevice() might trigger resetBluetoothSco() which Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
// will call setBluetoothScoOnInt(false, "resetBluetoothSco") + mBluetoothHeadsetDevice + " is not in audio connected mode");
setBtScoActiveDevice(activeDevice); return;
}
}
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;
}
} }
} }
mForcedUseForComm = AudioSystem.FORCE_BT_SCO; mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3397,9 +3396,8 @@ public class AudioService extends IAudioService.Stub
public int totalCount() { public int totalCount() {
synchronized(mScoClients) { synchronized(mScoClients) {
int count = 0; int count = 0;
int size = mScoClients.size(); for (ScoClient mScoClient : mScoClients) {
for (int i = 0; i < size; i++) { count += mScoClient.getCount();
count += mScoClients.get(i).getCount();
} }
return count; return count;
} }
@@ -3407,128 +3405,161 @@ public class AudioService extends IAudioService.Stub
private void requestScoState(int state, int scoAudioMode) { private void requestScoState(int state, int scoAudioMode) {
checkScoAudioState(); checkScoAudioState();
if (totalCount() == 0) { int clientCount = totalCount();
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { if (clientCount != 0) {
// Make sure that the state transitions to CONNECTING even if we cannot initiate Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
// the connection. + ", clientCount=" + clientCount);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); return;
// Accept SCO audio activation only in NORMAL audio mode or if the mode is }
// currently controlled by the same client process. if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
synchronized(mSetModeDeathHandlers) { // Make sure that the state transitions to CONNECTING even if we cannot initiate
if ((mSetModeDeathHandlers.isEmpty() || // the connection.
mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) && broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
(mScoAudioState == SCO_STATE_INACTIVE || // Accept SCO audio activation only in NORMAL audio mode or if the mode is
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) { // currently controlled by the same client process.
if (mScoAudioState == SCO_STATE_INACTIVE) { synchronized(mSetModeDeathHandlers) {
mScoAudioMode = scoAudioMode; int modeOwnerPid = mSetModeDeathHandlers.isEmpty()
if (scoAudioMode == SCO_MODE_UNDEFINED) { ? 0 : mSetModeDeathHandlers.get(0).getPid();
if (mBluetoothHeadsetDevice != null) { if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
mScoAudioMode = new Integer(Settings.Global.getInt( Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
mContentResolver, + modeOwnerPid + " != creatorPid " + mCreatorPid);
"bluetooth_sco_channel_"+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
mBluetoothHeadsetDevice.getAddress(), return;
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);
}
} }
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED && switch (mScoAudioState) {
(mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || case SCO_STATE_INACTIVE:
mScoAudioState == SCO_STATE_ACTIVATE_REQ)) { mScoAudioMode = scoAudioMode;
if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) { if (scoAudioMode == SCO_MODE_UNDEFINED) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) { mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
boolean status = false; if (mBluetoothHeadsetDevice != null) {
if (mScoAudioMode == SCO_MODE_RAW) { mScoAudioMode = Settings.Global.getInt(mContentResolver,
status = mBluetoothHeadset.disconnectAudio(); "bluetooth_sco_channel_"
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { + mBluetoothHeadsetDevice.getAddress(),
status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( SCO_MODE_VIRTUAL_CALL);
mBluetoothHeadsetDevice); if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
} else if (mScoAudioMode == SCO_MODE_VR) { mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
status = mBluetoothHeadset.stopVoiceRecognition( }
mBluetoothHeadsetDevice); }
} }
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; mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState( broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED); AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
} }
} else if (getBluetoothHeadset()) { break;
mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
} }
} 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; mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 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() { private void checkScoAudioState() {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && synchronized (mScoClients) {
mScoAudioState == SCO_STATE_INACTIVE && if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) mScoAudioState == SCO_STATE_INACTIVE &&
!= BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
} }
} }
private ScoClient getScoClient(IBinder cb, boolean create) { private ScoClient getScoClient(IBinder cb, boolean create) {
synchronized(mScoClients) { synchronized(mScoClients) {
ScoClient client = null; for (ScoClient existingClient : mScoClients) {
int size = mScoClients.size(); if (existingClient.getBinder() == cb) {
for (int i = 0; i < size; i++) { return existingClient;
client = mScoClients.get(i); }
if (client.getBinder() == cb)
return client;
} }
if (create) { if (create) {
client = new ScoClient(cb); ScoClient newClient = new ScoClient(cb);
mScoClients.add(client); mScoClients.add(newClient);
return newClient;
} }
return client; return null;
} }
} }
public void clearAllScoClients(int exceptPid, boolean stopSco) { public void clearAllScoClients(int exceptPid, boolean stopSco) {
synchronized(mScoClients) { synchronized(mScoClients) {
ScoClient savedClient = null; ScoClient savedClient = null;
int size = mScoClients.size(); for (ScoClient cl : mScoClients) {
for (int i = 0; i < size; i++) {
ScoClient cl = mScoClients.get(i);
if (cl.getPid() != exceptPid) { if (cl.getPid() != exceptPid) {
cl.clearCount(stopSco); cl.clearCount(stopSco);
} else { } else {
@@ -3565,10 +3596,17 @@ public class AudioService extends IAudioService.Stub
mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
if (mBluetoothHeadsetDevice != null) { if (mBluetoothHeadsetDevice != null) {
if (mBluetoothHeadset != null) { if (mBluetoothHeadset != null) {
if (!mBluetoothHeadset.stopVoiceRecognition( boolean status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice)) { mBluetoothHeadsetDevice, SCO_MODE_RAW)
sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, || disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
SENDMSG_REPLACE, 0, 0, null, 0); 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 && } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
getBluetoothHeadset()) { getBluetoothHeadset()) {
@@ -3581,6 +3619,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() { private void resetBluetoothSco() {
synchronized(mScoClients) { synchronized(mScoClients) {
clearAllScoClients(0, false); clearAllScoClients(0, false);
@@ -3638,11 +3704,9 @@ public class AudioService extends IAudioService.Stub
return result; return result;
} }
void setBtScoActiveDevice(BluetoothDevice btDevice) { private void setBtScoActiveDevice(BluetoothDevice btDevice) {
if (DEBUG_DEVICES) {
Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
}
synchronized (mScoClients) { synchronized (mScoClients) {
Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
if (!Objects.equals(btDevice, previousActiveDevice)) { if (!Objects.equals(btDevice, previousActiveDevice)) {
if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
@@ -3722,37 +3786,36 @@ public class AudioService extends IAudioService.Stub
boolean status = false; boolean status = false;
if (mBluetoothHeadsetDevice != null) { if (mBluetoothHeadsetDevice != null) {
switch (mScoAudioState) { switch (mScoAudioState) {
case SCO_STATE_ACTIVATE_REQ: case SCO_STATE_ACTIVATE_REQ:
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
if (mScoAudioMode == SCO_MODE_RAW) { mBluetoothHeadsetDevice, mScoAudioMode);
status = mBluetoothHeadset.connectAudio(); if (status) {
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( }
mBluetoothHeadsetDevice); break;
} else if (mScoAudioMode == SCO_MODE_VR) { case SCO_STATE_DEACTIVATE_REQ:
status = mBluetoothHeadset.startVoiceRecognition( status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice); mBluetoothHeadsetDevice, mScoAudioMode);
} if (status) {
break; mScoAudioState = SCO_STATE_DEACTIVATING;
case SCO_STATE_DEACTIVATE_REQ: }
if (mScoAudioMode == SCO_MODE_RAW) { break;
status = mBluetoothHeadset.disconnectAudio(); case SCO_STATE_DEACTIVATE_EXT_REQ:
} else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( mBluetoothHeadsetDevice, SCO_MODE_RAW) ||
mBluetoothHeadsetDevice); disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
} else if (mScoAudioMode == SCO_MODE_VR) { mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL) ||
status = mBluetoothHeadset.stopVoiceRecognition( disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice); mBluetoothHeadsetDevice, SCO_MODE_VR);
} if (status) {
break; mScoAudioState = SCO_STATE_DEACTIVATING;
case SCO_STATE_DEACTIVATE_EXT_REQ: }
status = mBluetoothHeadset.stopVoiceRecognition( break;
mBluetoothHeadsetDevice);
} }
} }
if (!status) { if (!status) {
sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, mScoAudioState = SCO_STATE_INACTIVE;
SENDMSG_REPLACE, 0, 0, null, 0); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
} }
} }
} }
@@ -6322,33 +6385,47 @@ public class AudioService extends IAudioService.Stub
if (!mScoClients.isEmpty() && if (!mScoClients.isEmpty() &&
(mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
mScoAudioState == SCO_STATE_ACTIVATE_REQ || mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) { mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
mScoAudioState == SCO_STATE_DEACTIVATING)) {
broadcast = true; 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_DEACTIVATE_EXT_REQ) { mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
} }
break; setBluetoothScoOn(true);
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: break;
scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
mScoAudioState = SCO_STATE_INACTIVE; setBluetoothScoOn(false);
clearAllScoClients(0, false); scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
break; // startBluetoothSco called after stopBluetoothSco
case BluetoothHeadset.STATE_AUDIO_CONNECTING: if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
mScoAudioState != SCO_STATE_DEACTIVATE_REQ && && connectBluetoothScoAudioHelper(mBluetoothHeadset,
mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} broadcast = false;
default: break;
// do not broadcast CONNECTING or invalid state }
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) { if (broadcast) {