summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java97
-rw-r--r--media/java/android/media/AudioManager.java14
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java425
3 files changed, 324 insertions, 212 deletions
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index a68f485f4374..0c91a2054b8b 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -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
+ *
+ * <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}.
*
- * @return true if successful false if there was some error such as there is no connected
- * headset
+ * <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.
+ *
+ * <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 there was some error such as there is no connected SCO
- * channel
+ * @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());
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a80c74197de2..b110ccf3a271 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4276,8 +4276,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>();
/**
@@ -4488,22 +4487,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));
+ }
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 4aee142e1464..8a643ed9a806 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -487,6 +487,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)
@@ -2466,9 +2468,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");
@@ -2477,8 +2483,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);
@@ -2522,17 +2528,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);
}
}
@@ -2941,28 +2951,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;
@@ -3140,9 +3139,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;
}
@@ -3150,128 +3148,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;
+ 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;
+ }
+ 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 && 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()) {
+ }
+ 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);
}
- } else {
+ 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;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ } else {
+ Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+ + " failed, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
- } else {
+ 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);
- }
- }
- } 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);
- }
+ 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 {
@@ -3308,10 +3339,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()) {
@@ -3324,6 +3362,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);
@@ -3381,11 +3447,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)) {
@@ -3465,37 +3529,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);
}
}
}
@@ -5757,33 +5820,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) {