summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPrzemyslaw Szczepaniak <pszczepaniak@google.com>2014-11-19 17:36:57 +0000
committerPrzemyslaw Szczepaniak <pszczepaniak@google.com>2014-12-09 11:06:06 +0000
commit4b73867a12a9339c7788e8949aac4a32d2eee22b (patch)
tree0b8611f24ab6c557f2e0b131d22bea824f10abe6
parentae13230b8799ac3c0902fef1057d7466f2161c44 (diff)
downloadframeworks_base-4b73867a12a9339c7788e8949aac4a32d2eee22b.zip
frameworks_base-4b73867a12a9339c7788e8949aac4a32d2eee22b.tar.gz
frameworks_base-4b73867a12a9339c7788e8949aac4a32d2eee22b.tar.bz2
Add UtteranceProgressListener#onStop callback
New UtteranceProgressListener callback that allows to detect a call to TextToSpeech#stop() (or QUEUE_FLUSH usage) from the same client, or a QUEUE_DESTROY usage from any other client (Talkback uses it to preempt other users of TextToSpeech queue). This change is required for seamless Books read aloud feature+Talkback usage. + Fixes for broken tests/TtsTests Bug: 17901521 Change-Id: I30d2f297bb7c8d05cbeb16f63e85c1be0cca5c84
-rw-r--r--api/current.txt1
-rw-r--r--core/java/android/speech/tts/ITextToSpeechCallback.aidl2
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java4
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java79
-rw-r--r--core/java/android/speech/tts/UtteranceProgressListener.java19
-rw-r--r--tests/TtsTests/src/com/android/speech/tts/MockableTextToSpeechService.java2
-rw-r--r--tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java29
7 files changed, 115 insertions, 21 deletions
diff --git a/api/current.txt b/api/current.txt
index 05aada4..b051768 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27752,6 +27752,7 @@ package android.speech.tts {
method public abstract deprecated void onError(java.lang.String);
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
+ method public void onStop(java.lang.String, boolean);
}
public class Voice implements android.os.Parcelable {
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 899515f..d785c3f 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -40,7 +40,7 @@ oneway interface ITextToSpeechCallback {
*
* @param utteranceId Unique id identifying synthesis request.
*/
- void onStop(String utteranceId);
+ void onStop(String utteranceId, boolean isStarted);
/**
* Tells the client that the synthesis has failed.
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index c59ca8a..06e9ce0 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2066,10 +2066,10 @@ public class TextToSpeech {
private boolean mEstablished;
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
- public void onStop(String utteranceId) throws RemoteException {
+ public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
UtteranceProgressListener listener = mUtteranceProgressListener;
if (listener != null) {
- listener.onDone(utteranceId);
+ listener.onStop(utteranceId, isStarted);
}
};
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 9bb7f02..02c9a36 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -455,10 +455,37 @@ public abstract class TextToSpeechService extends Service {
private class SynthHandler extends Handler {
private SpeechItem mCurrentSpeechItem = null;
+ private ArrayList<Object> mFlushedObjects = new ArrayList<Object>();
+ private boolean mFlushAll;
+
public SynthHandler(Looper looper) {
super(looper);
}
+ private void startFlushingSpeechItems(Object callerIdentity) {
+ synchronized (mFlushedObjects) {
+ if (callerIdentity == null) {
+ mFlushAll = true;
+ } else {
+ mFlushedObjects.add(callerIdentity);
+ }
+ }
+ }
+ private void endFlushingSpeechItems(Object callerIdentity) {
+ synchronized (mFlushedObjects) {
+ if (callerIdentity == null) {
+ mFlushAll = false;
+ } else {
+ mFlushedObjects.remove(callerIdentity);
+ }
+ }
+ }
+ private boolean isFlushed(SpeechItem speechItem) {
+ synchronized (mFlushedObjects) {
+ return mFlushAll || mFlushedObjects.contains(speechItem.getCallerIdentity());
+ }
+ }
+
private synchronized SpeechItem getCurrentSpeechItem() {
return mCurrentSpeechItem;
}
@@ -522,9 +549,13 @@ public abstract class TextToSpeechService extends Service {
Runnable runnable = new Runnable() {
@Override
public void run() {
- setCurrentSpeechItem(speechItem);
- speechItem.play();
- setCurrentSpeechItem(null);
+ if (isFlushed(speechItem)) {
+ speechItem.stop();
+ } else {
+ setCurrentSpeechItem(speechItem);
+ speechItem.play();
+ setCurrentSpeechItem(null);
+ }
}
};
Message msg = Message.obtain(this, runnable);
@@ -552,12 +583,14 @@ public abstract class TextToSpeechService extends Service {
*
* Called on a service binder thread.
*/
- public int stopForApp(Object callerIdentity) {
+ public int stopForApp(final Object callerIdentity) {
if (callerIdentity == null) {
return TextToSpeech.ERROR;
}
- removeCallbacksAndMessages(callerIdentity);
+ // Flush pending messages from callerIdentity
+ startFlushingSpeechItems(callerIdentity);
+
// This stops writing data to the file / or publishing
// items to the audio playback handler.
//
@@ -573,20 +606,39 @@ public abstract class TextToSpeechService extends Service {
// Remove any enqueued audio too.
mAudioPlaybackHandler.stopForApp(callerIdentity);
+ // Stop flushing pending messages
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ endFlushingSpeechItems(callerIdentity);
+ }
+ };
+ sendMessage(Message.obtain(this, runnable));
return TextToSpeech.SUCCESS;
}
public int stopAll() {
+ // Order to flush pending messages
+ startFlushingSpeechItems(null);
+
// Stop the current speech item unconditionally .
SpeechItem current = setCurrentSpeechItem(null);
if (current != null) {
current.stop();
}
- // Remove all other items from the queue.
- removeCallbacksAndMessages(null);
// Remove all pending playback as well.
mAudioPlaybackHandler.stop();
+ // Message to stop flushing pending messages
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ endFlushingSpeechItems(null);
+ }
+ };
+ sendMessage(Message.obtain(this, runnable));
+
+
return TextToSpeech.SUCCESS;
}
}
@@ -698,7 +750,6 @@ public abstract class TextToSpeechService extends Service {
return mCallerIdentity;
}
-
public int getCallerUid() {
return mCallerUid;
}
@@ -752,6 +803,10 @@ public abstract class TextToSpeechService extends Service {
protected synchronized boolean isStopped() {
return mStopped;
}
+
+ protected synchronized boolean isStarted() {
+ return mStarted;
+ }
}
/**
@@ -777,7 +832,7 @@ public abstract class TextToSpeechService extends Service {
public void dispatchOnStop() {
final String utteranceId = getUtteranceId();
if (utteranceId != null) {
- mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId);
+ mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
}
}
@@ -940,6 +995,8 @@ public abstract class TextToSpeechService extends Service {
// turn implies that synthesis would not have started.
synthesisCallback.stop();
TextToSpeechService.this.onStop();
+ } else {
+ dispatchOnStop();
}
}
@@ -1345,11 +1402,11 @@ public abstract class TextToSpeechService extends Service {
}
}
- public void dispatchOnStop(Object callerIdentity, String utteranceId) {
+ public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
if (cb == null) return;
try {
- cb.onStop(utteranceId);
+ cb.onStop(utteranceId, started);
} catch (RemoteException e) {
Log.e(TAG, "Callback onStop failed: " + e);
}
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 6769794..9eb22ef 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -60,6 +60,20 @@ public abstract class UtteranceProgressListener {
}
/**
+ * Called when an utterance has been stopped while in progress or flushed from the
+ * synthesis queue. This can happen if client calls {@link TextToSpeech#stop()}
+ * or use {@link TextToSpeech#QUEUE_FLUSH} as an argument in
+ * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.
+ *
+ * @param utteranceId the utterance ID of the utterance.
+ * @param isStarted If true, then utterance was interrupted while being synthesized
+ * and it's output is incomplete. If it's false, then utterance was flushed
+ * before the synthesis started.
+ */
+ public void onStop(String utteranceId, boolean isStarted) {
+ }
+
+ /**
* Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
* progress listener.
*
@@ -83,6 +97,11 @@ public abstract class UtteranceProgressListener {
// Left unimplemented, has no equivalent in the old
// API.
}
+
+ @Override
+ public void onStop(String utteranceId, boolean isStarted) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
};
}
}
diff --git a/tests/TtsTests/src/com/android/speech/tts/MockableTextToSpeechService.java b/tests/TtsTests/src/com/android/speech/tts/MockableTextToSpeechService.java
index 20648a4..06fbcdc 100644
--- a/tests/TtsTests/src/com/android/speech/tts/MockableTextToSpeechService.java
+++ b/tests/TtsTests/src/com/android/speech/tts/MockableTextToSpeechService.java
@@ -19,8 +19,10 @@ package com.android.speech.tts;
import android.speech.tts.SynthesisCallback;
import android.speech.tts.SynthesisRequest;
import android.speech.tts.TextToSpeechService;
+import android.util.Log;
import java.util.ArrayList;
+import java.util.logging.Logger;
public class MockableTextToSpeechService extends TextToSpeechService {
diff --git a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
index 78d4f96..faf6827 100644
--- a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
+++ b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
@@ -43,11 +43,18 @@ public class TextToSpeechTests extends InstrumentationTestCase {
IDelegate passThrough = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(passThrough);
+ // For the default voice selection
+ LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
+ .onIsLanguageAvailable(
+ LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
+ LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
+ .onLoadLanguage(
+ LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
+
blockingInitAndVerify(MOCK_ENGINE, TextToSpeech.SUCCESS);
assertEquals(MOCK_ENGINE, mTts.getCurrentEngine());
}
-
@Override
public void tearDown() {
if (mTts != null) {
@@ -77,7 +84,7 @@ public class TextToSpeechTests extends InstrumentationTestCase {
assertEquals(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE, mTts.setLanguage(new Locale("eng", "USA", "variant")));
LittleMock.verify(delegate, LittleMock.anyTimes()).onIsLanguageAvailable(
"eng", "USA", "variant");
- LittleMock.verify(delegate, LittleMock.times(1)).onLoadLanguage(
+ LittleMock.verify(delegate, LittleMock.anyTimes()).onLoadLanguage(
"eng", "USA", "variant");
}
@@ -147,26 +154,34 @@ public class TextToSpeechTests extends InstrumentationTestCase {
public void testDefaultLanguage_setsVoiceName() throws Exception {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
+ Locale defaultLocale = Locale.getDefault();
// ---------------------------------------------------------
// Test that default language also sets the default voice
// name
- LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
- LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
- LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage(
- LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
+ LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
+ when(delegate).onIsLanguageAvailable(
+ defaultLocale.getISO3Language(),
+ defaultLocale.getISO3Country().toUpperCase(),
+ defaultLocale.getVariant());
+ LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
+ when(delegate).onLoadLanguage(
+ defaultLocale.getISO3Language(),
+ defaultLocale.getISO3Country(),
+ defaultLocale.getVariant());
+
blockingCallSpeak("foo bar", delegate);
ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor();
LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(),
LittleMock.<SynthesisCallback>anyObject());
- Locale defaultLocale = Locale.getDefault();
assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage());
assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry());
assertEquals("", req.getValue().getVariant());
assertEquals(defaultLocale.toLanguageTag(), req.getValue().getVoiceName());
}
+
private void blockingCallSpeak(String speech, IDelegate mock) throws
InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);