@@ -260,6 +260,17 @@ private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
260260 return old ;
261261 }
262262
263+ private synchronized SpeechItem maybeRemoveCurrentSpeechItem (String callingApp ) {
264+ if (mCurrentSpeechItem != null &&
265+ TextUtils .equals (mCurrentSpeechItem .getCallingApp (), callingApp )) {
266+ SpeechItem current = mCurrentSpeechItem ;
267+ mCurrentSpeechItem = null ;
268+ return current ;
269+ }
270+
271+ return null ;
272+ }
273+
263274 public boolean isSpeaking () {
264275 return getCurrentSpeechItem () != null ;
265276 }
@@ -287,14 +298,9 @@ public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
287298 }
288299
289300 if (queueMode == TextToSpeech .QUEUE_FLUSH ) {
290- stop (speechItem .getCallingApp ());
301+ stopForApp (speechItem .getCallingApp ());
291302 } else if (queueMode == TextToSpeech .QUEUE_DESTROY ) {
292- // Stop the current speech item.
293- stop (speechItem .getCallingApp ());
294- // Remove all other items from the queue.
295- removeCallbacksAndMessages (null );
296- // Remove all pending playback as well.
297- mAudioPlaybackHandler .removeAllItems ();
303+ stopAll ();
298304 }
299305 Runnable runnable = new Runnable () {
300306 @ Override
@@ -305,7 +311,8 @@ public void run() {
305311 }
306312 };
307313 Message msg = Message .obtain (this , runnable );
308- // The obj is used to remove all callbacks from the given app in stop(String).
314+ // The obj is used to remove all callbacks from the given app in
315+ // stopForApp(String).
309316 //
310317 // Note that this string is interned, so the == comparison works.
311318 msg .obj = speechItem .getCallingApp ();
@@ -323,16 +330,21 @@ public void run() {
323330 *
324331 * Called on a service binder thread.
325332 */
326- public int stop (String callingApp ) {
333+ public int stopForApp (String callingApp ) {
327334 if (TextUtils .isEmpty (callingApp )) {
328335 return TextToSpeech .ERROR ;
329336 }
330337
331338 removeCallbacksAndMessages (callingApp );
332339 // This stops writing data to the file / or publishing
333340 // items to the audio playback handler.
334- SpeechItem current = setCurrentSpeechItem (null );
335- if (current != null && TextUtils .equals (callingApp , current .getCallingApp ())) {
341+ //
342+ // Note that the current speech item must be removed only if it
343+ // belongs to the callingApp, else the item will be "orphaned" and
344+ // not stopped correctly if a stop request comes along for the item
345+ // from the app it belongs to.
346+ SpeechItem current = maybeRemoveCurrentSpeechItem (callingApp );
347+ if (current != null ) {
336348 current .stop ();
337349 }
338350
@@ -341,6 +353,20 @@ public int stop(String callingApp) {
341353
342354 return TextToSpeech .SUCCESS ;
343355 }
356+
357+ public int stopAll () {
358+ // Stop the current speech item unconditionally.
359+ SpeechItem current = setCurrentSpeechItem (null );
360+ if (current != null ) {
361+ current .stop ();
362+ }
363+ // Remove all other items from the queue.
364+ removeCallbacksAndMessages (null );
365+ // Remove all pending playback as well.
366+ mAudioPlaybackHandler .removeAllItems ();
367+
368+ return TextToSpeech .SUCCESS ;
369+ }
344370 }
345371
346372 interface UtteranceCompletedDispatcher {
@@ -412,6 +438,10 @@ public void dispatchUtteranceCompleted() {
412438 }
413439 }
414440
441+ protected synchronized boolean isStopped () {
442+ return mStopped ;
443+ }
444+
415445 protected abstract int playImpl ();
416446
417447 protected abstract void stopImpl ();
@@ -473,7 +503,7 @@ public boolean isValid() {
473503 Log .w (TAG , "Got empty text" );
474504 return false ;
475505 }
476- if (mText .length () >= MAX_SPEECH_ITEM_CHAR_LENGTH ){
506+ if (mText .length () >= MAX_SPEECH_ITEM_CHAR_LENGTH ) {
477507 Log .w (TAG , "Text too long: " + mText .length () + " chars" );
478508 return false ;
479509 }
@@ -485,6 +515,11 @@ protected int playImpl() {
485515 AbstractSynthesisCallback synthesisCallback ;
486516 mEventLogger .onRequestProcessingStart ();
487517 synchronized (this ) {
518+ // stop() might have been called before we enter this
519+ // synchronized block.
520+ if (isStopped ()) {
521+ return TextToSpeech .ERROR ;
522+ }
488523 mSynthesisCallback = createSynthesisCallback ();
489524 synthesisCallback = mSynthesisCallback ;
490525 }
@@ -510,8 +545,13 @@ protected void stopImpl() {
510545 synchronized (this ) {
511546 synthesisCallback = mSynthesisCallback ;
512547 }
513- synthesisCallback .stop ();
514- TextToSpeechService .this .onStop ();
548+ if (synthesisCallback != null ) {
549+ // If the synthesis callback is null, it implies that we haven't
550+ // entered the synchronized(this) block in playImpl which in
551+ // turn implies that synthesis would not have started.
552+ synthesisCallback .stop ();
553+ TextToSpeechService .this .onStop ();
554+ }
515555 }
516556
517557 public String getLanguage () {
@@ -719,7 +759,7 @@ public int stop(String callingApp) {
719759 return TextToSpeech .ERROR ;
720760 }
721761
722- return mSynthHandler .stop (intern (callingApp ));
762+ return mSynthHandler .stopForApp (intern (callingApp ));
723763 }
724764
725765 public String [] getLanguage () {
@@ -811,7 +851,7 @@ public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
811851 synchronized (mAppToCallback ) {
812852 mAppToCallback .remove (packageName );
813853 }
814- mSynthHandler .stop (packageName );
854+ mSynthHandler .stopForApp (packageName );
815855 }
816856
817857 @ Override
0 commit comments