diff options
| -rw-r--r-- | core/java/android/bluetooth/BluetoothHeadset.java | 97 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 14 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/AudioService.java | 425 |
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) { |