import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
* call {@link #stopBluetoothSco()} to clear the request and turn down the bluetooth connection.
* <p>Even if a SCO connection is established, the following restrictions apply on audio
* output streams so that they can be routed to SCO headset:
+ * <p>NOTE: up to and including API version
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
+ * voice call to the bluetooth headset.
+ * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
+ * connection is established.
* <ul>
* <li> the stream type must be {@link #STREAM_VOICE_CALL} </li>
* <li> the format must be mono </li>
public void startBluetoothSco(){
IAudioService service = getService();
try {
- service.startBluetoothSco(mICallBack);
+ service.startBluetoothSco(mICallBack, mContext.getApplicationInfo().targetSdkVersion);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in startBluetoothSco", e);
}
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
// waiting for headset service to connect
private static final int SCO_STATE_DEACTIVATE_EXT_REQ = 4;
+ // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+ // originated from an app targeting an API version before JB MR2 and raw audio after that.
+ private int mScoAudioMode;
+ // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
+ private static final int SCO_MODE_VIRTUAL_CALL = 0;
+ // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
+ private static final int SCO_MODE_RAW = 1;
+
// Current connection state indicated by bluetooth headset
private int mScoConnectionState;
}
/** @see AudioManager#startBluetoothSco() */
- public void startBluetoothSco(IBinder cb){
+ public void startBluetoothSco(IBinder cb, int targetSdkVersion){
if (!checkAudioSettingsPermission("startBluetoothSco()") ||
!mBootCompleted) {
return;
// The caller identity must be cleared after getScoClient() because it is needed if a new
// client is created.
final long ident = Binder.clearCallingIdentity();
- client.incCount();
+ client.incCount(targetSdkVersion);
Binder.restoreCallingIdentity(ident);
}
}
}
- public void incCount() {
+ public void incCount(int targetSdkVersion) {
synchronized(mScoClients) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED);
+ requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, targetSdkVersion);
if (mStartcount == 0) {
try {
mCb.linkToDeath(this, 0);
Log.w(TAG, "decCount() going to 0 but not registered to binder");
}
}
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
}
}
}
}
mStartcount = 0;
if (stopSco) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
}
}
}
}
}
- private void requestScoState(int state) {
+ private void requestScoState(int state, int targetSdkVersion) {
checkScoAudioState();
if (totalCount() == 0) {
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
(mScoAudioState == SCO_STATE_INACTIVE ||
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_INACTIVE) {
+ mScoAudioMode =
+ (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
+ SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- if (mBluetoothHeadset.connectAudio()) {
+ boolean status;
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.connectAudio();
+ } else {
+ status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ }
+ if (status) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} else {
broadcastScoConnectionState(
mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- if (!mBluetoothHeadset.disconnectAudio()) {
+ boolean status;
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.disconnectAudio();
+ } else {
+ status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ }
+ if (!status) {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
switch (mScoAudioState) {
case SCO_STATE_ACTIVATE_REQ:
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- status = mBluetoothHeadset.connectAudio();
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.connectAudio();
+ } else {
+ status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ }
break;
case SCO_STATE_DEACTIVATE_REQ:
- status = mBluetoothHeadset.disconnectAudio();
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.disconnectAudio();
+ } else {
+ status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ }
break;
case SCO_STATE_DEACTIVATE_EXT_REQ:
status = mBluetoothHeadset.stopVoiceRecognition(