Merge "Fix regressions in TTS completion callbacks." into ics-mr1
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index 0194240..fd00dce 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -118,12 +118,26 @@
         if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
             stop(current);
         }
+
+        final MessageParams lastSynthesis = mLastSynthesisRequest;
+
+        if (lastSynthesis != null && lastSynthesis != current &&
+                TextUtils.equals(callingApp, lastSynthesis.getCallingApp())) {
+            stop(lastSynthesis);
+        }
     }
 
     synchronized public void removeAllItems() {
         if (DBG_THREADING) Log.d(TAG, "Removing all items");
         removeAllMessages();
-        stop(getCurrentParams());
+
+        final MessageParams current = getCurrentParams();
+        final MessageParams lastSynthesis = mLastSynthesisRequest;
+        stop(current);
+
+        if (lastSynthesis != null && lastSynthesis != current) {
+            stop(lastSynthesis);
+        }
     }
 
     /**
@@ -350,7 +364,7 @@
         // extra trouble to clean the data to prevent the AudioTrack resources
         // from being leaked.
         if (mLastSynthesisRequest != null) {
-            Log.w(TAG, "Error : Missing call to done() for request : " +
+            Log.e(TAG, "Error : Missing call to done() for request : " +
                     mLastSynthesisRequest);
             handleSynthesisDone(mLastSynthesisRequest);
         }
@@ -443,7 +457,11 @@
             audioTrack.release();
             params.setAudioTrack(null);
         }
-        params.getDispatcher().dispatchOnDone();
+        if (params.isError()) {
+            params.getDispatcher().dispatchOnError();
+        } else {
+            params.getDispatcher().dispatchOnDone();
+        }
         mLastSynthesisRequest = null;
         params.mLogger.onWriteData();
     }
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index ce3522b..91a3452 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -80,27 +80,23 @@
 
     @Override
     void stop() {
+        stopImpl(false);
+    }
+
+    void stopImpl(boolean wasError) {
         if (DBG) Log.d(TAG, "stop()");
 
         // Note that mLogger.mError might be true too at this point.
         mLogger.onStopped();
 
-        SynthesisMessageParams token = null;
+        SynthesisMessageParams token;
         synchronized (mStateLock) {
             if (mStopped) {
                 Log.w(TAG, "stop() called twice");
                 return;
             }
 
-            // mToken will be null if the engine encounters
-            // an error before it called start().
-            if (mToken == null) {
-                // In all other cases, mAudioTrackHandler.stop() will
-                // result in onComplete being called.
-                mLogger.onWriteData();
-            } else {
-                token = mToken;
-            }
+            token = mToken;
             mStopped = true;
         }
 
@@ -109,7 +105,24 @@
             // point it will write an additional buffer to the token - but we
             // won't worry about that because the audio playback queue will be cleared
             // soon after (see SynthHandler#stop(String).
+            token.setIsError(wasError);
             token.clearBuffers();
+            if (wasError) {
+                // Also clean up the audio track if an error occurs.
+                mAudioTrackHandler.enqueueSynthesisDone(token);
+            }
+        } else {
+            // This happens when stop() or error() were called before start() was.
+
+            // In all other cases, mAudioTrackHandler.stop() will
+            // result in onSynthesisDone being called, and we will
+            // write data there.
+            mLogger.onWriteData();
+
+            if (wasError) {
+                // We have to dispatch the error ourselves.
+                mDispatcher.dispatchOnError();
+            }
         }
     }
 
@@ -219,7 +232,7 @@
         // Currently, this call will not be logged if error( ) is called
         // before start.
         mLogger.onError();
-        stop();
+        stopImpl(true);
     }
 
 }
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index 0c0f033..ed66420 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -51,6 +51,7 @@
     int mAudioBufferSize;
     // Always synchronized on "this".
     int mUnconsumedBytes;
+    volatile boolean mIsError;
 
     private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
 
@@ -74,6 +75,7 @@
         mAudioTrack = null;
         mBytesWritten = 0;
         mAudioBufferSize = 0;
+        mIsError = false;
     }
 
     @Override
@@ -120,6 +122,14 @@
         return mAudioTrack;
     }
 
+    void setIsError(boolean isError) {
+        mIsError = isError;
+    }
+
+    boolean isError() {
+        return mIsError;
+    }
+
     // Must be called synchronized on this.
     private long getUnconsumedAudioLengthMs() {
         final int unconsumedFrames = mUnconsumedBytes / mBytesPerFrame;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 39922da..f82a659 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -509,6 +509,7 @@
     }
 
     class SynthesisSpeechItem extends SpeechItem {
+        // Never null.
         private final String mText;
         private final SynthesisRequest mSynthesisRequest;
         private final String[] mDefaultLocale;
@@ -532,8 +533,8 @@
 
         @Override
         public boolean isValid() {
-            if (TextUtils.isEmpty(mText)) {
-                Log.w(TAG, "Got empty text");
+            if (mText == null) {
+                Log.wtf(TAG, "Got null text");
                 return false;
             }
             if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index a04458a..cf0d22c 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -57,12 +57,16 @@
                 listener.onUtteranceCompleted(utteranceId);
             }
 
-            // The following methods are left unimplemented.
             @Override
-            public void onStart(String utteranceId) { }
+            public void onError(String utteranceId) {
+                listener.onUtteranceCompleted(utteranceId);
+            }
 
             @Override
-            public void onError(String utteranceId) { }
+            public void onStart(String utteranceId) {
+                // Left unimplemented, has no equivalent in the old
+                // API.
+            }
         };
     }
 }