am 405fcc87: am 754c72ed: Notifiy callers when a speech synthesis error occurs.

* commit '405fcc87b247d91ce2b54623f351e91b740813c0':
  Notifiy callers when a speech synthesis error occurs.
diff --git a/api/current.txt b/api/current.txt
index 37b888e..a7afa87 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18892,7 +18892,8 @@
     method public int playSilence(long, int, java.util.HashMap<java.lang.String, java.lang.String>);
     method public deprecated int setEngineByPackageName(java.lang.String);
     method public int setLanguage(java.util.Locale);
-    method public int setOnUtteranceCompletedListener(android.speech.tts.TextToSpeech.OnUtteranceCompletedListener);
+    method public deprecated int setOnUtteranceCompletedListener(android.speech.tts.TextToSpeech.OnUtteranceCompletedListener);
+    method public int setOnUtteranceProgressListener(android.speech.tts.UtteranceProgressListener);
     method public int setPitch(float);
     method public int setSpeechRate(float);
     method public void shutdown();
@@ -18965,6 +18966,13 @@
     method protected abstract void onSynthesizeText(android.speech.tts.SynthesisRequest, android.speech.tts.SynthesisCallback);
   }
 
+  public abstract class UtteranceProgressListener {
+    ctor public UtteranceProgressListener();
+    method public abstract void onDone(java.lang.String);
+    method public abstract void onError(java.lang.String);
+    method public abstract void onStart(java.lang.String);
+  }
+
 }
 
 package android.telephony {
diff --git a/core/java/android/speech/tts/AudioMessageParams.java b/core/java/android/speech/tts/AudioMessageParams.java
index 68d8738..29b4367 100644
--- a/core/java/android/speech/tts/AudioMessageParams.java
+++ b/core/java/android/speech/tts/AudioMessageParams.java
@@ -15,12 +15,12 @@
  */
 package android.speech.tts;
 
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
 
 class AudioMessageParams extends MessageParams {
     private final BlockingMediaPlayer mPlayer;
 
-    AudioMessageParams(UtteranceCompletedDispatcher dispatcher,
+    AudioMessageParams(UtteranceProgressDispatcher dispatcher,
             String callingApp, BlockingMediaPlayer player) {
         super(dispatcher, callingApp);
         mPlayer = player;
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index d970ae6..0194240 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -312,10 +312,11 @@
     private void handleSilence(MessageParams msg) {
         if (DBG) Log.d(TAG, "handleSilence()");
         SilenceMessageParams params = (SilenceMessageParams) msg;
+        params.getDispatcher().dispatchOnStart();
         if (params.getSilenceDurationMs() > 0) {
             params.getConditionVariable().block(params.getSilenceDurationMs());
         }
-        params.getDispatcher().dispatchUtteranceCompleted();
+        params.getDispatcher().dispatchOnDone();
         if (DBG) Log.d(TAG, "handleSilence() done.");
     }
 
@@ -323,11 +324,12 @@
     private void handleAudio(MessageParams msg) {
         if (DBG) Log.d(TAG, "handleAudio()");
         AudioMessageParams params = (AudioMessageParams) msg;
+        params.getDispatcher().dispatchOnStart();
         // Note that the BlockingMediaPlayer spawns a separate thread.
         //
         // TODO: This can be avoided.
         params.getPlayer().startAndWait();
-        params.getDispatcher().dispatchUtteranceCompleted();
+        params.getDispatcher().dispatchOnDone();
         if (DBG) Log.d(TAG, "handleAudio() done.");
     }
 
@@ -361,6 +363,7 @@
         if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]");
 
         param.setAudioTrack(audioTrack);
+        msg.getDispatcher().dispatchOnStart();
     }
 
     // More data available to be flushed to the audio track.
@@ -411,6 +414,7 @@
         final AudioTrack audioTrack = params.getAudioTrack();
 
         if (audioTrack == null) {
+            params.getDispatcher().dispatchOnError();
             return;
         }
 
@@ -439,7 +443,7 @@
             audioTrack.release();
             params.setAudioTrack(null);
         }
-        params.getDispatcher().dispatchUtteranceCompleted();
+        params.getDispatcher().dispatchOnDone();
         mLastSynthesisRequest = null;
         params.mLogger.onWriteData();
     }
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 40902ae..f0287d4 100755
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -21,5 +21,7 @@
  * {@hide}
  */
 oneway interface ITextToSpeechCallback {
-    void utteranceCompleted(String utteranceId);
+    void onStart(String utteranceId);
+    void onDone(String utteranceId);
+    void onError(String utteranceId);
 }
diff --git a/core/java/android/speech/tts/MessageParams.java b/core/java/android/speech/tts/MessageParams.java
index e7d6da3..de9cc07 100644
--- a/core/java/android/speech/tts/MessageParams.java
+++ b/core/java/android/speech/tts/MessageParams.java
@@ -15,22 +15,22 @@
  */
 package android.speech.tts;
 
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
 
 abstract class MessageParams {
     static final int TYPE_SYNTHESIS = 1;
     static final int TYPE_AUDIO = 2;
     static final int TYPE_SILENCE = 3;
 
-    private final UtteranceCompletedDispatcher mDispatcher;
+    private final UtteranceProgressDispatcher mDispatcher;
     private final String mCallingApp;
 
-    MessageParams(UtteranceCompletedDispatcher dispatcher, String callingApp) {
+    MessageParams(UtteranceProgressDispatcher dispatcher, String callingApp) {
         mDispatcher = dispatcher;
         mCallingApp = callingApp;
     }
 
-    UtteranceCompletedDispatcher getDispatcher() {
+    UtteranceProgressDispatcher getDispatcher() {
         return mDispatcher;
     }
 
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 0cca06a..ce3522b 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -15,7 +15,7 @@
  */
 package android.speech.tts;
 
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
 import android.util.Log;
 
 /**
@@ -62,12 +62,12 @@
 
     private volatile boolean mDone = false;
 
-    private final UtteranceCompletedDispatcher mDispatcher;
+    private final UtteranceProgressDispatcher mDispatcher;
     private final String mCallingApp;
     private final EventLogger mLogger;
 
     PlaybackSynthesisCallback(int streamType, float volume, float pan,
-            AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher,
+            AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
             String callingApp, EventLogger logger) {
         mStreamType = streamType;
         mVolume = volume;
diff --git a/core/java/android/speech/tts/SilenceMessageParams.java b/core/java/android/speech/tts/SilenceMessageParams.java
index 7a4ff1c..9909126 100644
--- a/core/java/android/speech/tts/SilenceMessageParams.java
+++ b/core/java/android/speech/tts/SilenceMessageParams.java
@@ -16,13 +16,13 @@
 package android.speech.tts;
 
 import android.os.ConditionVariable;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
 
 class SilenceMessageParams extends MessageParams {
     private final ConditionVariable mCondVar = new ConditionVariable();
     private final long mSilenceDurationMs;
 
-    SilenceMessageParams(UtteranceCompletedDispatcher dispatcher,
+    SilenceMessageParams(UtteranceProgressDispatcher dispatcher,
             String callingApp, long silenceDurationMs) {
         super(dispatcher, callingApp);
         mSilenceDurationMs = silenceDurationMs;
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index 779721e..0c0f033 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -17,7 +17,7 @@
 
 import android.media.AudioFormat;
 import android.media.AudioTrack;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
 
 import java.util.LinkedList;
 
@@ -56,7 +56,7 @@
 
     SynthesisMessageParams(int streamType, int sampleRate,
             int audioFormat, int channelCount,
-            float volume, float pan, UtteranceCompletedDispatcher dispatcher,
+            float volume, float pan, UtteranceProgressDispatcher dispatcher,
             String callingApp, EventLogger logger) {
         super(dispatcher, callingApp);
 
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index fdc2570..38699ea 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -482,7 +482,7 @@
     private OnInitListener mInitListener;
     // Written from an unspecified application thread, read from
     // a binder thread.
-    private volatile OnUtteranceCompletedListener mUtteranceCompletedListener;
+    private volatile UtteranceProgressListener mUtteranceProgressListener;
     private final Object mStartLock = new Object();
 
     private String mRequestedEngine;
@@ -1146,9 +1146,28 @@
      * @param listener The listener to use.
      *
      * @return {@link #ERROR} or {@link #SUCCESS}.
+     *
+     * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
+     *        instead.
      */
+    @Deprecated
     public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
-        mUtteranceCompletedListener = listener;
+        mUtteranceProgressListener = UtteranceProgressListener.from(listener);
+        return TextToSpeech.SUCCESS;
+    }
+
+    /**
+     * Sets the listener that will be notified of various events related to the
+     * synthesis of a given utterance.
+     *
+     * See {@link UtteranceProgressListener} and
+     * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
+     *
+     * @param listener the listener to use.
+     * @return {@link #ERROR} or {@link #SUCCESS}
+     */
+    public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
+        mUtteranceProgressListener = listener;
         return TextToSpeech.SUCCESS;
     }
 
@@ -1204,10 +1223,26 @@
         private ITextToSpeechService mService;
         private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
             @Override
-            public void utteranceCompleted(String utteranceId) {
-                OnUtteranceCompletedListener listener = mUtteranceCompletedListener;
+            public void onDone(String utteranceId) {
+                UtteranceProgressListener listener = mUtteranceProgressListener;
                 if (listener != null) {
-                    listener.onUtteranceCompleted(utteranceId);
+                    listener.onDone(utteranceId);
+                }
+            }
+
+            @Override
+            public void onError(String utteranceId) {
+                UtteranceProgressListener listener = mUtteranceProgressListener;
+                if (listener != null) {
+                    listener.onError(utteranceId);
+                }
+            }
+
+            @Override
+            public void onStart(String utteranceId) {
+                UtteranceProgressListener listener = mUtteranceProgressListener;
+                if (listener != null) {
+                    listener.onStart(utteranceId);
                 }
             }
         };
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 83b6d4c..39922da 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -306,6 +306,7 @@
          */
         public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
             if (!speechItem.isValid()) {
+                speechItem.dispatchOnError();
                 return TextToSpeech.ERROR;
             }
 
@@ -332,6 +333,7 @@
                 return TextToSpeech.SUCCESS;
             } else {
                 Log.w(TAG, "SynthThread has quit");
+                speechItem.dispatchOnError();
                 return TextToSpeech.ERROR;
             }
         }
@@ -381,14 +383,16 @@
         }
     }
 
-    interface UtteranceCompletedDispatcher {
-        public void dispatchUtteranceCompleted();
+    interface UtteranceProgressDispatcher {
+        public void dispatchOnDone();
+        public void dispatchOnStart();
+        public void dispatchOnError();
     }
 
     /**
      * An item in the synth thread queue.
      */
-    private abstract class SpeechItem implements UtteranceCompletedDispatcher {
+    private abstract class SpeechItem implements UtteranceProgressDispatcher {
         private final String mCallingApp;
         protected final Bundle mParams;
         private boolean mStarted = false;
@@ -443,10 +447,27 @@
             stopImpl();
         }
 
-        public void dispatchUtteranceCompleted() {
+        @Override
+        public void dispatchOnDone() {
             final String utteranceId = getUtteranceId();
             if (!TextUtils.isEmpty(utteranceId)) {
-                mCallbacks.dispatchUtteranceCompleted(getCallingApp(), utteranceId);
+                mCallbacks.dispatchOnDone(getCallingApp(), utteranceId);
+            }
+        }
+
+        @Override
+        public void dispatchOnStart() {
+            final String utteranceId = getUtteranceId();
+            if (!TextUtils.isEmpty(utteranceId)) {
+                mCallbacks.dispatchOnStart(getCallingApp(), utteranceId);
+            }
+        }
+
+        @Override
+        public void dispatchOnError() {
+            final String utteranceId = getUtteranceId();
+            if (!TextUtils.isEmpty(utteranceId)) {
+                mCallbacks.dispatchOnError(getCallingApp(), utteranceId);
             }
         }
 
@@ -617,9 +638,12 @@
 
         @Override
         protected int playImpl() {
+            dispatchOnStart();
             int status = super.playImpl();
             if (status == TextToSpeech.SUCCESS) {
-                dispatchUtteranceCompleted();
+                dispatchOnDone();
+            } else {
+                dispatchOnError();
             }
             return status;
         }
@@ -856,16 +880,34 @@
             }
         }
 
-        public void dispatchUtteranceCompleted(String packageName, String utteranceId) {
-            ITextToSpeechCallback cb;
-            synchronized (mAppToCallback) {
-                cb = mAppToCallback.get(packageName);
-            }
+        public void dispatchOnDone(String packageName, String utteranceId) {
+            ITextToSpeechCallback cb = getCallbackFor(packageName);
             if (cb == null) return;
             try {
-                cb.utteranceCompleted(utteranceId);
+                cb.onDone(utteranceId);
             } catch (RemoteException e) {
-                Log.e(TAG, "Callback failed: " + e);
+                Log.e(TAG, "Callback onDone failed: " + e);
+            }
+        }
+
+        public void dispatchOnStart(String packageName, String utteranceId) {
+            ITextToSpeechCallback cb = getCallbackFor(packageName);
+            if (cb == null) return;
+            try {
+                cb.onStart(utteranceId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback onStart failed: " + e);
+            }
+
+        }
+
+        public void dispatchOnError(String packageName, String utteranceId) {
+            ITextToSpeechCallback cb = getCallbackFor(packageName);
+            if (cb == null) return;
+            try {
+                cb.onError(utteranceId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback onError failed: " + e);
             }
         }
 
@@ -886,6 +928,15 @@
             }
         }
 
+        private ITextToSpeechCallback getCallbackFor(String packageName) {
+            ITextToSpeechCallback cb;
+            synchronized (mAppToCallback) {
+                cb = mAppToCallback.get(packageName);
+            }
+
+            return cb;
+        }
+
     }
 
 }
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
new file mode 100644
index 0000000..a04458a
--- /dev/null
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -0,0 +1,68 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package android.speech.tts;
+
+/**
+ * Listener for events relating to the progress of an utterance through
+ * the synthesis queue. Each utterance is associated with a call to
+ * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} with an
+ * associated utterance identifier, as per {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
+ *
+ * The callbacks specified in this method can be called from multiple threads.
+ */
+public abstract class UtteranceProgressListener {
+    /**
+     * Called when an utterance "starts" as perceived by the caller. This will
+     * be soon before audio is played back in the case of a {@link TextToSpeech#speak}
+     * or before the first bytes of a file are written to storage in the case
+     * of {@link TextToSpeech#synthesizeToFile}.
+     *
+     * @param utteranceId the utterance ID of the utterance.
+     */
+    public abstract void onStart(String utteranceId);
+
+    /**
+     * Called when an utterance has successfully completed processing.
+     * All audio will have been played back by this point for audible output, and all
+     * output will have been written to disk for file synthesis requests.
+     *
+     * This request is guaranteed to be called after {@link #onStart(String)}.
+     *
+     * @param utteranceId the utterance ID of the utterance.
+     */
+    public abstract void onDone(String utteranceId);
+
+    /**
+     * Called when an error has occurred during processing. This can be called
+     * at any point in the synthesis process. Note that there might be calls
+     * to {@link #onStart(String)} for specified utteranceId but there will never
+     * be a call to both {@link #onDone(String)} and {@link #onError(String)} for
+     * the same utterance.
+     *
+     * @param utteranceId the utterance ID of the utterance.
+     */
+    public abstract void onError(String utteranceId);
+
+    /**
+     * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
+     * progress listener.
+     *
+     * @hide
+     */
+    static UtteranceProgressListener from(
+            final TextToSpeech.OnUtteranceCompletedListener listener) {
+        return new UtteranceProgressListener() {
+            @Override
+            public synchronized void onDone(String utteranceId) {
+                listener.onUtteranceCompleted(utteranceId);
+            }
+
+            // The following methods are left unimplemented.
+            @Override
+            public void onStart(String utteranceId) { }
+
+            @Override
+            public void onError(String utteranceId) { }
+        };
+    }
+}