Merge "NetInitiatedActivity: support AUTO response feature for SUPL IOT"
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index dea708a..96864c4 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -17,6 +17,7 @@
 
 import android.media.AudioFormat;
 import android.media.AudioTrack;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.Iterator;
@@ -25,6 +26,7 @@
 
 class AudioPlaybackHandler {
     private static final String TAG = "TTS.AudioPlaybackHandler";
+    private static final boolean DBG_THREADING = false;
     private static final boolean DBG = false;
 
     private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
@@ -64,70 +66,105 @@
      * Stops all synthesis for a given {@code token}. If the current token
      * is currently being processed, an effort will be made to stop it but
      * that is not guaranteed.
+     *
+     * NOTE: This assumes that all other messages in the queue with {@code token}
+     * have been removed already.
+     *
+     * NOTE: Must be called synchronized on {@code AudioPlaybackHandler.this}.
      */
-    synchronized public void stop(MessageParams token) {
+    private void stop(MessageParams token) {
         if (token == null) {
             return;
         }
 
-        removeMessages(token);
+        if (DBG) Log.d(TAG, "Stopping token : " + token);
 
         if (token.getType() == MessageParams.TYPE_SYNTHESIS) {
             AudioTrack current = ((SynthesisMessageParams) token).getAudioTrack();
             if (current != null) {
                 // Stop the current audio track if it's still playing.
-                // The audio track is thread safe in this regard.
+                // The audio track is thread safe in this regard. The current
+                // handleSynthesisDataAvailable call will return soon after this
+                // call.
                 current.stop();
             }
+            // This is safe because PlaybackSynthesisCallback#stop would have
+            // been called before this method, and will no longer enqueue any
+            // audio for this token.
+            //
+            // (Even if it did, all it would result in is a warning message).
             mQueue.add(new ListEntry(SYNTHESIS_DONE, token, HIGH_PRIORITY));
-        } else  {
-            final MessageParams current = getCurrentParams();
-
-            if (current != null) {
-                if (token.getType() == MessageParams.TYPE_AUDIO) {
-                    ((AudioMessageParams) current).getPlayer().stop();
-                } else if (token.getType() == MessageParams.TYPE_SILENCE) {
-                    ((SilenceMessageParams) current).getConditionVariable().open();
-                }
-            }
+        } else if (token.getType() == MessageParams.TYPE_AUDIO) {
+            ((AudioMessageParams) token).getPlayer().stop();
+            // No cleanup required for audio messages.
+        } else if (token.getType() == MessageParams.TYPE_SILENCE) {
+            ((SilenceMessageParams) token).getConditionVariable().open();
+            // No cleanup required for silence messages.
         }
     }
 
+    // -----------------------------------------------------
+    // Methods that add and remove elements from the queue. These do not
+    // need to be synchronized strictly speaking, but they make the behaviour
+    // a lot more predictable. (though it would still be correct without
+    // synchronization).
+    // -----------------------------------------------------
+
     synchronized public void removePlaybackItems(String callingApp) {
+        if (DBG_THREADING) Log.d(TAG, "Removing all callback items for : " + callingApp);
         removeMessages(callingApp);
-        stop(getCurrentParams());
+
+        final MessageParams current = getCurrentParams();
+        if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
+            stop(current);
+        }
     }
 
     synchronized public void removeAllItems() {
+        if (DBG_THREADING) Log.d(TAG, "Removing all items");
         removeAllMessages();
         stop(getCurrentParams());
     }
 
     /**
+     * @return false iff the queue is empty and no queue item is currently
+     *        being handled, true otherwise.
+     */
+    public boolean isSpeaking() {
+        return (mQueue.peek() != null) || (mCurrentParams != null);
+    }
+
+    /**
      * Shut down the audio playback thread.
      */
     synchronized public void quit() {
+        removeAllMessages();
         stop(getCurrentParams());
         mQueue.add(new ListEntry(SHUTDOWN, null, HIGH_PRIORITY));
     }
 
-    void enqueueSynthesisStart(SynthesisMessageParams token) {
+    synchronized void enqueueSynthesisStart(SynthesisMessageParams token) {
+        if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis start : " + token);
         mQueue.add(new ListEntry(SYNTHESIS_START, token));
     }
 
-    void enqueueSynthesisDataAvailable(SynthesisMessageParams token) {
+    synchronized void enqueueSynthesisDataAvailable(SynthesisMessageParams token) {
+        if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis data available : " + token);
         mQueue.add(new ListEntry(SYNTHESIS_DATA_AVAILABLE, token));
     }
 
-    void enqueueSynthesisDone(SynthesisMessageParams token) {
+    synchronized void enqueueSynthesisDone(SynthesisMessageParams token) {
+        if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis done : " + token);
         mQueue.add(new ListEntry(SYNTHESIS_DONE, token));
     }
 
-    void enqueueAudio(AudioMessageParams token) {
+    synchronized void enqueueAudio(AudioMessageParams token) {
+        if (DBG_THREADING) Log.d(TAG, "Enqueuing audio : " + token);
         mQueue.add(new ListEntry(PLAY_AUDIO, token));
     }
 
-    void enqueueSilence(SilenceMessageParams token) {
+    synchronized void enqueueSilence(SilenceMessageParams token) {
+        if (DBG_THREADING) Log.d(TAG, "Enqueuing silence : " + token);
         mQueue.add(new ListEntry(PLAY_SILENCE, token));
     }
 
@@ -172,26 +209,6 @@
     }
 
     /*
-     * Remove all messages from the queue that contain the supplied token.
-     * Note that the Iterator is thread safe, and other methods can safely
-     * continue adding to the queue at this point.
-     */
-    synchronized private void removeMessages(MessageParams token) {
-        if (token == null) {
-            return;
-        }
-
-        Iterator<ListEntry> it = mQueue.iterator();
-
-        while (it.hasNext()) {
-            final ListEntry current = it.next();
-            if (current.mMessage == token) {
-                it.remove();
-            }
-        }
-    }
-
-    /*
      * Atomically clear the queue of all messages.
      */
     synchronized private void removeAllMessages() {
@@ -255,6 +272,13 @@
     }
 
     private void setCurrentParams(MessageParams p) {
+        if (DBG_THREADING) {
+            if (p != null) {
+                Log.d(TAG, "Started handling :" + p);
+            } else {
+                Log.d(TAG, "End handling : " + mCurrentParams);
+            }
+        }
         mCurrentParams = p;
     }
 
@@ -345,7 +369,7 @@
     private void handleSynthesisDataAvailable(MessageParams msg) {
         final SynthesisMessageParams param = (SynthesisMessageParams) msg;
         if (param.getAudioTrack() == null) {
-            Log.w(TAG, "Error : null audio track in handleDataAvailable.");
+            Log.w(TAG, "Error : null audio track in handleDataAvailable : " + param);
             return;
         }
 
diff --git a/core/java/android/speech/tts/MessageParams.java b/core/java/android/speech/tts/MessageParams.java
index 4c1b6d2..e7d6da3 100644
--- a/core/java/android/speech/tts/MessageParams.java
+++ b/core/java/android/speech/tts/MessageParams.java
@@ -38,5 +38,10 @@
         return mCallingApp;
     }
 
+    @Override
+    public String toString() {
+        return "MessageParams[" + hashCode() + "]";
+    }
+
     abstract int getType();
 }
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 04bd745..7dbf1ac 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -90,12 +90,10 @@
                 Log.w(TAG, "stop() called twice");
                 return;
             }
+
             // mToken will be null if the engine encounters
             // an error before it called start().
-            if (mToken != null) {
-                mAudioTrackHandler.stop(mToken);
-                mToken = null;
-            } else {
+            if (mToken == null) {
                 // In all other cases, mAudioTrackHandler.stop() will
                 // result in onComplete being called.
                 mLogger.onWriteData();
@@ -158,7 +156,7 @@
         }
 
         synchronized (mStateLock) {
-            if (mToken == null) {
+            if (mToken == null || mStopped) {
                 return TextToSpeech.ERROR;
             }
 
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 32ca226..5126e48 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -109,6 +109,11 @@
     /**
      * Broadcast Action: The TextToSpeech synthesizer has completed processing
      * of all the text in the speech queue.
+     *
+     * Note that this notifies callers when the <b>engine</b> has finished has
+     * processing text data. Audio playback might not have completed (or even started)
+     * at this point. If you wish to be notified when this happens, see
+     * {@link OnUtteranceCompletedListener}.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
@@ -576,6 +581,14 @@
                 service.setCallback(getPackageName(), null);
                 service.stop(getPackageName());
                 mServiceConnection.disconnect();
+                // Context#unbindService does not result in a call to
+                // ServiceConnection#onServiceDisconnected. As a result, the
+                // service ends up being destroyed (if there are no other open
+                // connections to it) but the process lives on and the
+                // ServiceConnection continues to refer to the destroyed service.
+                //
+                // This leads to tons of log spam about SynthThread being dead.
+                mServiceConnection = null;
                 mCurrentEngine = null;
                 return null;
             }
@@ -796,7 +809,10 @@
     }
 
     /**
-     * Checks whether the TTS engine is busy speaking.
+     * Checks whether the TTS engine is busy speaking. Note that a speech item is
+     * considered complete once it's audio data has been sent to the audio mixer, or
+     * written to a file. There might be a finite lag between this point, and when
+     * the audio hardware completes playback.
      *
      * @return {@code true} if the TTS engine is speaking.
      */
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 010c155..1926c92 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -282,6 +282,8 @@
             if (current != null) {
                 current.stop();
             }
+
+            // The AudioPlaybackHandler will be destroyed by the caller.
         }
 
         /**
@@ -337,6 +339,8 @@
             }
 
             removeCallbacksAndMessages(callingApp);
+            // This stops writing data to the file / or publishing
+            // items to the audio playback handler.
             SpeechItem current = setCurrentSpeechItem(null);
             if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
                 current.stop();
@@ -628,9 +632,7 @@
 
         @Override
         protected void stopImpl() {
-            if (mToken != null) {
-                mAudioPlaybackHandler.stop(mToken);
-            }
+            // Do nothing.
         }
     }
 
@@ -657,9 +659,7 @@
 
         @Override
         protected void stopImpl() {
-            if (mToken != null) {
-                mAudioPlaybackHandler.stop(mToken);
-            }
+            // Do nothing.
         }
     }
 
@@ -719,7 +719,7 @@
         }
 
         public boolean isSpeaking() {
-            return mSynthHandler.isSpeaking();
+            return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
         }
 
         public int stop(String callingApp) {
@@ -767,10 +767,6 @@
             mCallbacks.setCallback(packageName, cb);
         }
 
-        private boolean isDefault(String lang, String country, String variant) {
-            return Locale.getDefault().equals(new Locale(lang, country, variant));
-        }
-
         private String intern(String in) {
             // The input parameter will be non null.
             return in.intern();
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index 5a5330d..0251baf 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -58,8 +58,10 @@
 }
 
 NuPlayer::HTTPLiveSource::~HTTPLiveSource() {
-    mLiveSession->disconnect();
-    mLiveLooper->stop();
+    if (mLiveSession != NULL) {
+        mLiveSession->disconnect();
+        mLiveLooper->stop();
+    }
 }
 
 void NuPlayer::HTTPLiveSource::start() {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 7cd8b6c..c6fca2c 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -118,9 +118,15 @@
             mPlayer->start();
 
             if (mStartupSeekTimeUs >= 0) {
-                mPlayer->seekToAsync(mStartupSeekTimeUs);
+                if (mStartupSeekTimeUs == 0) {
+                    notifySeekComplete();
+                } else {
+                    mPlayer->seekToAsync(mStartupSeekTimeUs);
+                }
+
                 mStartupSeekTimeUs = -1;
             }
+
             break;
         }
         case PLAYING:
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index ca61b3d..73b3d5b 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -494,6 +494,12 @@
 
         bool firstTime = (mPlaylist == NULL);
 
+        if ((ssize_t)bandwidthIndex != mPrevBandwidthIndex) {
+            // If we switch bandwidths, do not pay any heed to whether
+            // playlists changed since the last time...
+            mPlaylist.clear();
+        }
+
         bool unchanged;
         sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &unchanged);
         if (playlist == NULL) {