Merge "Issue 2416481: Support Voice Dialer over BT SCO."
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,8 @@ interface IAudioService {
|
||||
void registerMediaButtonEventReceiver(in ComponentName eventReceiver);
|
||||
|
||||
void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
|
||||
|
||||
void startBluetoothSco(IBinder cb);
|
||||
|
||||
void stopBluetoothSco(IBinder cb);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user