Merge "Issue 2416481: Support Voice Dialer over BT SCO."

This commit is contained in:
Eric Laurent
2010-03-18 10:01:10 -07:00
committed by Android (Google) Code Review
3 changed files with 326 additions and 1 deletions

View File

@@ -690,6 +690,132 @@ public class AudioManager {
}
}
//====================================================================
// Bluetooth SCO control
/**
* @hide
* TODO unhide for SDK
* Sticky broadcast intent action indicating that the bluetoooth SCO audio
* connection state has changed. The intent contains on extra {@link EXTRA_SCO_AUDIO_STATE}
* indicating the new state which is either {@link #SCO_AUDIO_STATE_DISCONNECTED}
* or {@link #SCO_AUDIO_STATE_CONNECTED}
*
* @see #startBluetoothSco()
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SCO_AUDIO_STATE_CHANGED =
"android.media.SCO_AUDIO_STATE_CHANGED";
/**
* @hide
* TODO unhide for SDK
* Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} containing the new
* bluetooth SCO connection state.
*/
public static final String EXTRA_SCO_AUDIO_STATE =
"android.media.extra.SCO_AUDIO_STATE";
/**
* @hide
* TODO unhide for SDK
* Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
* SCO audio channel is not established
*/
public static final int SCO_AUDIO_STATE_DISCONNECTED = 0;
/**
* @hide
* TODO unhide for SDK
* Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
* SCO audio channel is established
*/
public static final int SCO_AUDIO_STATE_CONNECTED = 1;
/**
* @hide
* TODO unhide for SDK
* Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that
* there was an error trying to obtain the state
*/
public static final int SCO_AUDIO_STATE_ERROR = -1;
/**
* @hide
* TODO unhide for SDK
* Indicates if current platform supports use of SCO for off call use cases.
* Application wanted to use bluetooth SCO audio when the phone is not in call
* must first call thsi method to make sure that the platform supports this
* feature.
* @return true if bluetooth SCO can be used for audio when not in call
* false otherwise
* @see #startBluetoothSco()
*/
public boolean isBluetoothScoAvailableOffCall() {
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_bluetooth_sco_off_call);
}
/**
* @hide
* TODO unhide for SDK
* Start bluetooth SCO audio connection.
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
* <p>This method can be used by applications wanting to send and received audio
* to/from a bluetooth SCO headset while the phone is not in call.
* <p>As the SCO connection establishment can take several seconds,
* applications should not rely on the connection to be available when the method
* returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED}
* and wait for the state to be {@link #SCO_AUDIO_STATE_CONNECTED}.
* <p>As the connection is not guaranteed to succeed, applications must wait for this intent with
* a timeout.
* <p>When finished with the SCO connection or if the establishment times out,
* the application must 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:
* - the stream type must be {@link #STREAM_VOICE_CALL} or {@link #STREAM_BLUETOOTH_SCO}
* - the format must be mono
* - the sampling must be 16kHz or 8kHz
* <p>The following restrictions apply on input streams:
* - the format must be mono
* - the sampling must be 8kHz
*
* <p>Note that the phone application always has the priority on the usage of the SCO
* connection for telephony. If this method is called while the phone is in call
* it will be ignored. Similarly, if a call is received or sent while an application
* is using the SCO connection, the connection will be lost for the application and NOT
* returned automatically when the call ends.
* @see #stopBluetoothSco()
* @see #ACTION_SCO_AUDIO_STATE_CHANGED
*/
public void startBluetoothSco(){
IAudioService service = getService();
try {
service.startBluetoothSco(mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in startBluetoothSco", e);
}
}
/**
* @hide
* TODO unhide for SDK
* Stop bluetooth SCO audio connection.
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
* <p>This method must be called by applications having requested the use of
* bluetooth SCO audio with {@link #startBluetoothSco()}
* when finished with the SCO connection or if the establishment times out.
* @see #startBluetoothSco()
*/
public void stopBluetoothSco(){
IAudioService service = getService();
try {
service.stopBluetoothSco(mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in stopBluetoothSco", e);
}
}
/**
* Request use of Bluetooth SCO headset for communications.
* <p>
@@ -1171,7 +1297,7 @@ public class AudioManager {
* When losing focus, listeners can use the duration hint to decide what
* behavior to adopt when losing focus. A music player could for instance elect to duck its
* music stream for transient focus losses, and pause otherwise.
* @param focusChange one of {@link AudioManager#AUDIOFOCUS_GAIN},
* @param focusChange one of {@link AudioManager#AUDIOFOCUS_GAIN},
* {@link AudioManager#AUDIOFOCUS_LOSS}, {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}.
*/
public void onAudioFocusChanged(int focusChange);

View File

@@ -240,6 +240,15 @@ public class AudioService extends IAudioService.Stub {
// The last process to have called setMode() is at the top of the list.
private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
// List of clients having issued a SCO start request
private ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
// BluetoothHeadset API to control SCO connection
private BluetoothHeadset mBluetoothHeadset;
// Bluetooth headset connection state
private boolean mBluetoothHeadsetConnected;
///////////////////////////////////////////////////////////////////////////
// Construction
///////////////////////////////////////////////////////////////////////////
@@ -267,12 +276,17 @@ public class AudioService extends IAudioService.Stub {
AudioSystem.setErrorCallback(mAudioSystemCallback);
loadSoundEffects();
mBluetoothHeadsetConnected = false;
mBluetoothHeadset = new BluetoothHeadset(context,
mBluetoothHeadsetServiceListener);
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(Intent.ACTION_HEADSET_PLUG);
intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
intentFilter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
context.registerReceiver(mReceiver, intentFilter);
// Register for media button intent broadcasts.
@@ -705,6 +719,10 @@ public class AudioService extends IAudioService.Stub {
mSetModeDeathHandlers.add(0, hdlr);
hdlr.setMode(mode);
}
if (mode != AudioSystem.MODE_NORMAL) {
clearAllScoClients();
}
}
}
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -909,6 +927,157 @@ public class AudioService extends IAudioService.Stub {
}
}
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb){
if (!checkAudioSettingsPermission("startBluetoothSco()")) {
return;
}
ScoClient client = getScoClient(cb);
client.incCount();
}
/** @see AudioManager#stopBluetoothSco() */
public void stopBluetoothSco(IBinder cb){
if (!checkAudioSettingsPermission("stopBluetoothSco()")) {
return;
}
ScoClient client = getScoClient(cb);
client.decCount();
}
private class ScoClient implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
private int mStartcount; // number of SCO connections started by this client
ScoClient(IBinder cb) {
mCb = cb;
mStartcount = 0;
}
public void binderDied() {
synchronized(mScoClients) {
Log.w(TAG, "SCO client died");
int index = mScoClients.indexOf(this);
if (index < 0) {
Log.w(TAG, "unregistered SCO client died");
} else {
clearCount(true);
mScoClients.remove(this);
}
}
}
public void incCount() {
synchronized(mScoClients) {
requestScoState(BluetoothHeadset.AUDIO_STATE_CONNECTED);
if (mStartcount == 0) {
try {
mCb.linkToDeath(this, 0);
} catch (RemoteException e) {
// client has already died!
Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death");
}
}
mStartcount++;
}
}
public void decCount() {
synchronized(mScoClients) {
if (mStartcount == 0) {
Log.w(TAG, "ScoClient.decCount() already 0");
} else {
mStartcount--;
if (mStartcount == 0) {
mCb.unlinkToDeath(this, 0);
}
requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
}
}
}
public void clearCount(boolean stopSco) {
synchronized(mScoClients) {
mStartcount = 0;
mCb.unlinkToDeath(this, 0);
if (stopSco) {
requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
}
}
}
public int getCount() {
return mStartcount;
}
public IBinder getBinder() {
return mCb;
}
public int totalCount() {
synchronized(mScoClients) {
int count = 0;
int size = mScoClients.size();
for (int i = 0; i < size; i++) {
count += mScoClients.get(i).getCount();
}
return count;
}
}
private void requestScoState(int state) {
if (totalCount() == 0 &&
mBluetoothHeadsetConnected &&
AudioService.this.mMode == AudioSystem.MODE_NORMAL) {
if (state == BluetoothHeadset.AUDIO_STATE_CONNECTED) {
mBluetoothHeadset.startVoiceRecognition();
} else {
mBluetoothHeadset.stopVoiceRecognition();
}
}
}
}
public ScoClient getScoClient(IBinder cb) {
synchronized(mScoClients) {
ScoClient client;
int size = mScoClients.size();
for (int i = 0; i < size; i++) {
client = mScoClients.get(i);
if (client.getBinder() == cb)
return client;
}
client = new ScoClient(cb);
mScoClients.add(client);
return client;
}
}
public void clearAllScoClients() {
synchronized(mScoClients) {
int size = mScoClients.size();
for (int i = 0; i < size; i++) {
mScoClients.get(i).clearCount(false);
}
}
}
private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener =
new BluetoothHeadset.ServiceListener() {
public void onServiceConnected() {
if (mBluetoothHeadset != null &&
mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) {
mBluetoothHeadsetConnected = true;
}
}
public void onServiceDisconnected() {
if (mBluetoothHeadset != null &&
mBluetoothHeadset.getState() == BluetoothHeadset.STATE_DISCONNECTED) {
mBluetoothHeadsetConnected = false;
clearAllScoClients();
}
}
};
///////////////////////////////////////////////////////////////////////////
// Internal methods
@@ -1577,11 +1746,14 @@ public class AudioService extends IAudioService.Stub {
AudioSystem.DEVICE_STATE_UNAVAILABLE,
address);
mConnectedDevices.remove(device);
mBluetoothHeadsetConnected = false;
clearAllScoClients();
} else if (!isConnected && state == BluetoothHeadset.STATE_CONNECTED) {
AudioSystem.setDeviceConnectionState(device,
AudioSystem.DEVICE_STATE_AVAILABLE,
address);
mConnectedDevices.put(new Integer(device), address);
mBluetoothHeadsetConnected = true;
}
} else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", 0);
@@ -1614,6 +1786,29 @@ public class AudioService extends IAudioService.Stub {
mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), "");
}
}
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE,
BluetoothHeadset.STATE_ERROR);
synchronized (mScoClients) {
if (!mScoClients.isEmpty()) {
switch (state) {
case BluetoothHeadset.AUDIO_STATE_CONNECTED:
state = AudioManager.SCO_AUDIO_STATE_CONNECTED;
break;
case BluetoothHeadset.AUDIO_STATE_DISCONNECTED:
state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
break;
default:
state = AudioManager.SCO_AUDIO_STATE_ERROR;
break;
}
if (state != AudioManager.SCO_AUDIO_STATE_ERROR) {
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
mContext.sendStickyBroadcast(newIntent);
}
}
}
}
}
}

View File

@@ -82,4 +82,8 @@ interface IAudioService {
void registerMediaButtonEventReceiver(in ComponentName eventReceiver);
void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
void startBluetoothSco(IBinder cb);
void stopBluetoothSco(IBinder cb);
}