Merge "Remove need for onActivityResult from KeyChain API"
diff --git a/api/current.txt b/api/current.txt
index 8c61922..6854965 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9687,7 +9687,8 @@
method public void unloadSoundEffects();
method public void unregisterMediaButtonEventReceiver(android.content.ComponentName);
field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
- field public static final java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
+ field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED";
field public static final int ADJUST_LOWER = -1; // 0xffffffff
field public static final int ADJUST_RAISE = 1; // 0x1
field public static final int ADJUST_SAME = 0; // 0x0
@@ -9700,6 +9701,7 @@
field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
field public static final java.lang.String EXTRA_RINGER_MODE = "android.media.EXTRA_RINGER_MODE";
+ field public static final java.lang.String EXTRA_SCO_AUDIO_PREVIOUS_STATE = "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
field public static final java.lang.String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE";
field public static final java.lang.String EXTRA_VIBRATE_SETTING = "android.media.EXTRA_VIBRATE_SETTING";
field public static final java.lang.String EXTRA_VIBRATE_TYPE = "android.media.EXTRA_VIBRATE_TYPE";
@@ -9736,6 +9738,7 @@
field public static final deprecated int ROUTE_HEADSET = 8; // 0x8
field public static final deprecated int ROUTE_SPEAKER = 2; // 0x2
field public static final int SCO_AUDIO_STATE_CONNECTED = 1; // 0x1
+ field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
field public static final int STREAM_ALARM = 4; // 0x4
diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
index 6f4c15b..d698b54 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -18,6 +18,7 @@
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.os.Bundle;
+import android.os.Handler;
import android.util.Log;
/**
@@ -49,16 +50,20 @@
private final float mPan;
private final Object mStateLock = new Object();
- private AudioTrack mAudioTrack = null;
+ private final Handler mAudioTrackHandler;
+ private volatile AudioTrack mAudioTrack = null;
private boolean mStopped = false;
private boolean mDone = false;
+ private volatile boolean mWriteErrorOccured;
PlaybackSynthesisRequest(String text, Bundle params,
- int streamType, float volume, float pan) {
+ int streamType, float volume, float pan, Handler audioTrackHandler) {
super(text, params);
mStreamType = streamType;
mVolume = volume;
mPan = pan;
+ mAudioTrackHandler = audioTrackHandler;
+ mWriteErrorOccured = false;
}
@Override
@@ -70,14 +75,28 @@
}
}
+ // Always guarded by mStateLock.
private void cleanUp() {
if (DBG) Log.d(TAG, "cleanUp()");
- if (mAudioTrack != null) {
- mAudioTrack.flush();
- mAudioTrack.stop();
- mAudioTrack.release();
- mAudioTrack = null;
+ if (mAudioTrack == null) {
+ return;
}
+
+ final AudioTrack audioTrack = mAudioTrack;
+ mAudioTrack = null;
+
+ // Clean up on the audiotrack handler thread.
+ //
+ // NOTE: It isn't very clear whether AudioTrack is thread safe.
+ // If it is we can clean up on the current (synthesis) thread.
+ mAudioTrackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ audioTrack.flush();
+ audioTrack.stop();
+ audioTrack.release();
+ }
+ });
}
@Override
@@ -146,10 +165,15 @@
Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
+ offset + "," + length + ")");
}
- if (length > getMaxBufferSize()) {
- throw new IllegalArgumentException("buffer is too large (" + length + " bytes)");
+ if (length > getMaxBufferSize() || length <= 0) {
+ throw new IllegalArgumentException("buffer is too large or of zero length (" +
+ + length + " bytes)");
}
synchronized (mStateLock) {
+ if (mWriteErrorOccured) {
+ if (DBG) Log.d(TAG, "Error writing to audio track, count < 0");
+ return TextToSpeech.ERROR;
+ }
if (mStopped) {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
@@ -158,22 +182,33 @@
Log.e(TAG, "audioAvailable(): Not started");
return TextToSpeech.ERROR;
}
- int playState = mAudioTrack.getPlayState();
- if (playState == AudioTrack.PLAYSTATE_STOPPED) {
- if (DBG) Log.d(TAG, "AudioTrack stopped, restarting");
- mAudioTrack.play();
- }
- // TODO: loop until all data is written?
- if (DBG) Log.d(TAG, "AudioTrack.write()");
- int count = mAudioTrack.write(buffer, offset, length);
- if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count);
- if (count < 0) {
- Log.e(TAG, "Writing to AudioTrack failed: " + count);
- cleanUp();
- return TextToSpeech.ERROR;
- } else {
- return TextToSpeech.SUCCESS;
- }
+ final AudioTrack audioTrack = mAudioTrack;
+ // Sigh, another copy.
+ final byte[] bufferCopy = new byte[length];
+ System.arraycopy(buffer, offset, bufferCopy, 0, length);
+
+ mAudioTrackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ int playState = audioTrack.getPlayState();
+ if (playState == AudioTrack.PLAYSTATE_STOPPED) {
+ if (DBG) Log.d(TAG, "AudioTrack stopped, restarting");
+ audioTrack.play();
+ }
+ // TODO: loop until all data is written?
+ if (DBG) Log.d(TAG, "AudioTrack.write()");
+ int count = audioTrack.write(bufferCopy, 0, bufferCopy.length);
+ // The semantics of this change very slightly. Earlier, we would
+ // report an error immediately, Now we will return an error on
+ // the next API call, usually done( ) or another audioAvailable( )
+ // call.
+ if (count < 0) {
+ mWriteErrorOccured = true;
+ }
+ }
+ });
+
+ return TextToSpeech.SUCCESS;
}
}
@@ -181,6 +216,10 @@
public int done() {
if (DBG) Log.d(TAG, "done()");
synchronized (mStateLock) {
+ if (mWriteErrorOccured) {
+ if (DBG) Log.d(TAG, "Error writing to audio track, count < 0");
+ return TextToSpeech.ERROR;
+ }
if (mStopped) {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index f32474f..717dde8 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -51,6 +51,7 @@
private static final String SYNTH_THREAD_NAME = "SynthThread";
private SynthHandler mSynthHandler;
+ private Handler mAudioTrackHandler;
private CallbackMap mCallbacks;
@@ -63,6 +64,10 @@
synthThread.start();
mSynthHandler = new SynthHandler(synthThread.getLooper());
+ HandlerThread audioTrackThread = new HandlerThread("TTS.audioTrackThread");
+ audioTrackThread.start();
+ mAudioTrackHandler = new Handler(audioTrackThread.getLooper());
+
mCallbacks = new CallbackMap();
// Load default language
@@ -75,6 +80,7 @@
// Tell the synthesizer to stop
mSynthHandler.quit();
+ mAudioTrackHandler.getLooper().quit();
// Unregister all callbacks.
mCallbacks.kill();
@@ -444,7 +450,7 @@
protected SynthesisRequest createSynthesisRequest() {
return new PlaybackSynthesisRequest(mText, mParams,
- getStreamType(), getVolume(), getPan());
+ getStreamType(), getVolume(), getPan(), mAudioTrackHandler);
}
private void setRequestParams(SynthesisRequest request) {
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ccb3518..7f4cc81 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1852,7 +1852,7 @@
<!-- Do not translate. WebView User Agent string -->
<string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
- AppleWebKit/534.16 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.16</string>
+ AppleWebKit/534.20 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.20</string>
<!-- Do not translate. WebView User Agent targeted content -->
<string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e1daede..253010c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -828,29 +828,64 @@
* or {@link #SCO_AUDIO_STATE_CONNECTED}
*
* @see #startBluetoothSco()
+ * @deprecated Use {@link #ACTION_SCO_AUDIO_STATE_UPDATED} instead
*/
+ @Deprecated
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SCO_AUDIO_STATE_CHANGED =
"android.media.SCO_AUDIO_STATE_CHANGED";
+
+ /**
+ * Sticky broadcast intent action indicating that the bluetoooth SCO audio
+ * connection state has been updated.
+ * <p>This intent has two extras:
+ * <ul>
+ * <li> {@link #EXTRA_SCO_AUDIO_STATE} - The new SCO audio state. </li>
+ * <li> {@link #EXTRA_SCO_AUDIO_PREVIOUS_STATE}- The previous SCO audio state. </li>
+ * </ul>
+ * <p> EXTRA_SCO_AUDIO_STATE or EXTRA_SCO_AUDIO_PREVIOUS_STATE can be any of:
+ * <ul>
+ * <li> {@link #SCO_AUDIO_STATE_DISCONNECTED}, </li>
+ * <li> {@link #SCO_AUDIO_STATE_CONNECTING} or </li>
+ * <li> {@link #SCO_AUDIO_STATE_CONNECTED}, </li>
+ * </ul>
+ * @see #startBluetoothSco()
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCO_AUDIO_STATE_UPDATED =
+ "android.media.ACTION_SCO_AUDIO_STATE_UPDATED";
+
/**
- * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} containing the new
- * bluetooth SCO connection state.
+ * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} or
+ * {@link #ACTION_SCO_AUDIO_STATE_UPDATED} containing the new bluetooth SCO connection state.
*/
public static final String EXTRA_SCO_AUDIO_STATE =
"android.media.extra.SCO_AUDIO_STATE";
/**
- * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
- * SCO audio channel is not established
+ * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_UPDATED} containing the previous
+ * bluetooth SCO connection state.
+ */
+ public static final String EXTRA_SCO_AUDIO_PREVIOUS_STATE =
+ "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
+
+ /**
+ * Value for extra EXTRA_SCO_AUDIO_STATE or EXTRA_SCO_AUDIO_PREVIOUS_STATE
+ * indicating that the SCO audio channel is not established
*/
public static final int SCO_AUDIO_STATE_DISCONNECTED = 0;
/**
- * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
- * SCO audio channel is established
+ * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} or {@link #EXTRA_SCO_AUDIO_PREVIOUS_STATE}
+ * indicating that the SCO audio channel is established
*/
public static final int SCO_AUDIO_STATE_CONNECTED = 1;
/**
- * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that
+ * Value for extra EXTRA_SCO_AUDIO_STATE or EXTRA_SCO_AUDIO_PREVIOUS_STATE
+ * indicating that the SCO audio channel is being established
+ */
+ public static final int SCO_AUDIO_STATE_CONNECTING = 2;
+ /**
+ * Value for extra EXTRA_SCO_AUDIO_STATE indicating that
* there was an error trying to obtain the state
*/
public static final int SCO_AUDIO_STATE_ERROR = -1;
@@ -878,29 +913,37 @@
* 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}
+ * returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_UPDATED}
* 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>As the ACTION_SCO_AUDIO_STATE_UPDATED intent is sticky, the application can check the SCO
+ * audio state before calling startBluetoothSco() by reading the intent returned by the receiver
+ * registration. If the state is already CONNECTED, no state change will be received via the
+ * intent after calling startBluetoothSco(). It is however useful to call startBluetoothSco()
+ * so that the connection stays active in case the current initiator stops the connection.
+ * <p>Unless the connection is already active as described above, the state will always
+ * transition from DISCONNECTED to CONNECTING and then either to CONNECTED if the connection
+ * succeeds or back to DISCONNECTED if the connection fails (e.g no headset is connected).
+ * <p>When finished with the SCO connection or if the establishment fails, 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}
- * - the format must be mono
- * - the sampling must be 16kHz or 8kHz
+ * <ul>
+ * <li> the stream type must be {@link #STREAM_VOICE_CALL} </li>
+ * <li> the format must be mono </li>
+ * <li> the sampling must be 16kHz or 8kHz </li>
+ * </ul>
* <p>The following restrictions apply on input streams:
- * - the format must be mono
- * - the sampling must be 8kHz
- *
+ * <ul>
+ * <li> the format must be mono </li>
+ * <li> the sampling must be 8kHz </li>
+ * </ul>
* <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
+ * @see #ACTION_SCO_AUDIO_STATE_UPDATED
*/
public void startBluetoothSco(){
IAudioService service = getService();
@@ -917,7 +960,7 @@
* {@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.
+ * when finished with the SCO connection or if connection fails.
* @see #startBluetoothSco()
*/
public void stopBluetoothSco(){
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 504cfde..4e77fcb 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -112,9 +112,12 @@
private static final int MSG_LOAD_SOUND_EFFECTS = 9;
private static final int MSG_SET_FORCE_USE = 10;
private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 11;
-
+ private static final int MSG_BT_HEADSET_CNCT_FAILED = 12;
private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
+ // Timeout for connection to bluetooth headset service
+ private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
@@ -273,11 +276,22 @@
private int mScoAudioState;
// SCO audio state is not active
private static final int SCO_STATE_INACTIVE = 0;
+ // SCO audio activation request waiting for headset service to connect
+ private static final int SCO_STATE_ACTIVATE_REQ = 1;
// SCO audio state is active or starting due to a local request to start a virtual call
- private static final int SCO_STATE_ACTIVE_INTERNAL = 1;
+ 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 state is active due to an action in BT handsfree (either voice recognition or
// in call audio)
private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+ // Deactivation request for all SCO connections (initiated by audio mode change)
+ // waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_EXT_REQ = 4;
+
+ // Current connection state indicated by bluetooth headset
+ private int mScoConnectionState;
// true if boot sequence has been completed
private boolean mBootCompleted;
@@ -335,13 +349,6 @@
AudioSystem.setErrorCallback(mAudioSystemCallback);
- mBluetoothHeadsetDevice = null;
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
-
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(Intent.ACTION_HEADSET_PLUG);
@@ -768,17 +775,7 @@
if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
AudioService.this.mMode = mode;
if (mode != AudioSystem.MODE_NORMAL) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else {
- clearAllScoClients(mCb, true);
- }
- }
+ disconnectBluetoothSco(mCb);
}
}
}
@@ -856,16 +853,7 @@
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode
if (mode != AudioSystem.MODE_NORMAL) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- mBluetoothHeadset.stopVoiceRecognition(mBluetoothHeadsetDevice);
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else {
- clearAllScoClients(cb, true);
- }
- }
+ disconnectBluetoothSco(cb);
}
}
}
@@ -1213,7 +1201,8 @@
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb){
- if (!checkAudioSettingsPermission("startBluetoothSco()")) {
+ if (!checkAudioSettingsPermission("startBluetoothSco()") ||
+ !mBootCompleted) {
return;
}
ScoClient client = getScoClient(cb, true);
@@ -1222,7 +1211,8 @@
/** @see AudioManager#stopBluetoothSco() */
public void stopBluetoothSco(IBinder cb){
- if (!checkAudioSettingsPermission("stopBluetoothSco()")) {
+ if (!checkAudioSettingsPermission("stopBluetoothSco()") ||
+ !mBootCompleted) {
return;
}
ScoClient client = getScoClient(cb, false);
@@ -1322,25 +1312,57 @@
}
private void requestScoState(int state) {
- if (mBluetoothHeadset == null) {
- return;
- }
-
checkScoAudioState();
-
- if (totalCount() == 0 &&
- mBluetoothHeadsetDevice != null) {
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client.
- if ((AudioService.this.mMode == AudioSystem.MODE_NORMAL ||
- mSetModeDeathHandlers.get(0).getBinder() == mCb) &&
- state == BluetoothHeadset.STATE_AUDIO_CONNECTED &&
- mScoAudioState == SCO_STATE_INACTIVE) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- mBluetoothHeadset.startScoUsingVirtualVoiceCall(mBluetoothHeadsetDevice);
+ 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.
+ if ((AudioService.this.mMode == AudioSystem.MODE_NORMAL ||
+ mSetModeDeathHandlers.get(0).getBinder() == mCb) &&
+ mBluetoothHeadsetDevice != null &&
+ (mScoAudioState == SCO_STATE_INACTIVE ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
+ if (mScoAudioState == SCO_STATE_INACTIVE) {
+ if (mBluetoothHeadset != null) {
+ if (mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ }
+ } else {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ }
+ } else {
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
- mScoAudioState == SCO_STATE_ACTIVE_INTERNAL){
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(mBluetoothHeadsetDevice);
+ mBluetoothHeadsetDevice != null &&
+ (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
+ mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
+ if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
+ if (mBluetoothHeadset != null) {
+ if (!mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice)) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ }
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
}
}
}
@@ -1348,7 +1370,7 @@
private void checkScoAudioState() {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
- mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState == SCO_STATE_INACTIVE &&
mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
@@ -1391,10 +1413,72 @@
}
}
+ private boolean getBluetoothHeadset() {
+ boolean result = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
+ // If we could not get a bluetooth headset proxy, send a failure message
+ // without delay to reset the SCO audio state and clear SCO clients.
+ // If we could get a proxy, send a delayed failure message that will reset our state
+ // in case we don't receive onServiceConnected().
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+ SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+ return result;
+ }
+
+ private void disconnectBluetoothSco(IBinder exceptBinder) {
+ synchronized(mScoClients) {
+ checkScoAudioState();
+ if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+ if (mBluetoothHeadsetDevice != null) {
+ if (mBluetoothHeadset != null) {
+ if (!mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice) ||
+ !mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice)) {
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+ SENDMSG_REPLACE, 0, 0, null, 0);
+ }
+ } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
+ getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_EXT_REQ;
+ }
+ }
+ } else {
+ clearAllScoClients(exceptBinder, true);
+ }
+ }
+ }
+
+ private void resetBluetoothSco() {
+ synchronized(mScoClients) {
+ clearAllScoClients(null, false);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+
+ private void broadcastScoConnectionState(int state) {
+ if (state != mScoConnectionState) {
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+ mScoConnectionState);
+ mContext.sendStickyBroadcast(newIntent);
+ mScoConnectionState = state;
+ }
+ }
+
private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
synchronized (mScoClients) {
+ // Discard timeout message
+ mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
mBluetoothHeadset = (BluetoothHeadset) proxy;
List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
if (deviceList.size() > 0) {
@@ -1402,19 +1486,41 @@
} else {
mBluetoothHeadsetDevice = null;
}
+ // Refresh SCO audio state
+ checkScoAudioState();
+ // Continue pending action if any
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+ boolean status = false;
+ if (mBluetoothHeadsetDevice != null) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ break;
+ case SCO_STATE_DEACTIVATE_EXT_REQ:
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice) &&
+ mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ }
+ }
+ if (!status) {
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+ SENDMSG_REPLACE, 0, 0, null, 0);
+ }
+ }
}
}
public void onServiceDisconnected(int profile) {
synchronized (mScoClients) {
- if (mBluetoothHeadset != null) {
- List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
- if (devices.size() == 0) {
- mBluetoothHeadsetDevice = null;
- clearAllScoClients(null, false);
- mScoAudioState = SCO_STATE_INACTIVE;
- }
- mBluetoothHeadset = null;
- }
+ mBluetoothHeadset = null;
}
}
};
@@ -2041,6 +2147,10 @@
case MSG_PERSIST_MEDIABUTTONRECEIVER:
persistMediaButtonReceiver( (ComponentName) msg.obj );
break;
+
+ case MSG_BT_HEADSET_CNCT_FAILED:
+ resetBluetoothSco();
+ break;
}
}
}
@@ -2241,8 +2351,7 @@
address);
mConnectedDevices.remove(device);
mBluetoothHeadsetDevice = null;
- clearAllScoClients(null, false);
- mScoAudioState = SCO_STATE_INACTIVE;
+ resetBluetoothSco();
} else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
AudioSystem.setDeviceConnectionState(device,
AudioSystem.DEVICE_STATE_AVAILABLE,
@@ -2332,26 +2441,34 @@
}
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
- int audioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ int state = AudioManager.SCO_AUDIO_STATE_ERROR;
synchronized (mScoClients) {
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (!mScoClients.isEmpty() && mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
+ // broadcast intent if the connection was initated by AudioService
+ if (!mScoClients.isEmpty() &&
+ (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
+ mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
broadcast = true;
}
switch (btState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- audioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
+ state = 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:
- audioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
mScoAudioState = SCO_STATE_INACTIVE;
clearAllScoClients(null, false);
break;
case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
default:
@@ -2361,14 +2478,23 @@
}
}
if (broadcast) {
+ broadcastScoConnectionState(state);
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, audioState);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
mContext.sendStickyBroadcast(newIntent);
}
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
mBootCompleted = true;
sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SHARED_MSG, SENDMSG_NOOP,
0, 0, null, 0);
+
+ mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ resetBluetoothSco();
+ getBluetoothHeadset();
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);