1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.speech.tts;
18 
19 import android.speech.tts.SynthesisCallback;
20 import android.speech.tts.SynthesisRequest;
21 import android.speech.tts.TextToSpeech;
22 import android.test.InstrumentationTestCase;
23 
24 import com.android.speech.tts.MockableTextToSpeechService.IDelegate;
25 import com.google.testing.littlemock.ArgumentCaptor;
26 import com.google.testing.littlemock.Behaviour;
27 import com.google.testing.littlemock.LittleMock;
28 import junit.framework.Assert;
29 
30 import java.util.Locale;
31 import java.util.concurrent.Callable;
32 import java.util.concurrent.CountDownLatch;
33 import java.util.concurrent.TimeUnit;
34 
35 public class TextToSpeechTests extends InstrumentationTestCase {
36     private static final String MOCK_ENGINE = "com.android.speech.tts";
37     private static final String MOCK_PACKAGE = "com.android.speech.tts.__testpackage__";
38 
39     private TextToSpeech mTts;
40 
41     @Override
setUp()42     public void setUp() throws Exception {
43         IDelegate passThrough = LittleMock.mock(IDelegate.class);
44         MockableTextToSpeechService.setMocker(passThrough);
45 
46         // For the default voice selection
47         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
48             .onIsLanguageAvailable(
49                     LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
50         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
51             .onLoadLanguage(
52                     LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
53 
54         blockingInitAndVerify(MOCK_ENGINE, TextToSpeech.SUCCESS);
55         assertEquals(MOCK_ENGINE, mTts.getCurrentEngine());
56     }
57 
58     @Override
tearDown()59     public void tearDown() {
60         if (mTts != null) {
61             mTts.shutdown();
62         }
63     }
64 
testEngineInitialized()65     public void testEngineInitialized() throws Exception {
66         // Fail on an engine that doesn't exist.
67         blockingInitAndVerify("__DOES_NOT_EXIST__", TextToSpeech.ERROR);
68 
69         // Also, the "current engine" must be null
70         assertNull(mTts.getCurrentEngine());
71     }
72 
testSetLanguage_delegation()73     public void testSetLanguage_delegation() {
74         IDelegate delegate = LittleMock.mock(IDelegate.class);
75         MockableTextToSpeechService.setMocker(delegate);
76 
77         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onIsLanguageAvailable(
78                 "eng", "USA", "variant");
79         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onLoadLanguage(
80                 "eng", "USA", "variant");
81 
82         // Test 1 :Tests that calls to onLoadLanguage( ) are delegated through to the
83         // service without any caching or intermediate steps.
84         assertEquals(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE, mTts.setLanguage(new Locale("eng", "USA", "variant")));
85         LittleMock.verify(delegate, LittleMock.anyTimes()).onIsLanguageAvailable(
86             "eng", "USA", "variant");
87         LittleMock.verify(delegate, LittleMock.anyTimes()).onLoadLanguage(
88             "eng", "USA", "variant");
89     }
90 
testSetLanguage_availableLanguage()91     public void testSetLanguage_availableLanguage() throws Exception {
92         IDelegate delegate = LittleMock.mock(IDelegate.class);
93         MockableTextToSpeechService.setMocker(delegate);
94 
95         // ---------------------------------------------------------
96         // Test 2 : Tests that when the language is successfully set
97         // like above (returns LANG_COUNTRY_AVAILABLE). That the
98         // request language changes from that point on.
99         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
100                 "eng", "USA", "variant");
101         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
102                 "eng", "USA", "");
103         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage(
104                 "eng", "USA", "");
105         mTts.setLanguage(new Locale("eng", "USA", "variant"));
106         blockingCallSpeak("foo bar", delegate);
107         ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor();
108         LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(),
109                 LittleMock.<SynthesisCallback>anyObject());
110 
111         assertEquals("eng", req.getValue().getLanguage());
112         assertEquals("USA", req.getValue().getCountry());
113         assertEquals("", req.getValue().getVariant());
114         assertEquals("en-US", req.getValue().getVoiceName());
115     }
116 
testSetLanguage_unavailableLanguage()117     public void testSetLanguage_unavailableLanguage() throws Exception {
118         IDelegate delegate = LittleMock.mock(IDelegate.class);
119         MockableTextToSpeechService.setMocker(delegate);
120 
121         // ---------------------------------------------------------
122         // TEST 3 : Tests that the language that is set does not change when the
123         // engine reports it could not load the specified language.
124         LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
125                 delegate).onIsLanguageAvailable("fra", "FRA", "");
126         LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
127                 delegate).onLoadLanguage("fra", "FRA", "");
128         mTts.setLanguage(Locale.FRANCE);
129         blockingCallSpeak("le fou barre", delegate);
130         ArgumentCaptor<SynthesisRequest> req2 = LittleMock.createCaptor();
131         LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req2.capture(),
132                         LittleMock.<SynthesisCallback>anyObject());
133 
134         // The params are basically unchanged.
135         assertEquals("eng", req2.getValue().getLanguage());
136         assertEquals("USA", req2.getValue().getCountry());
137         assertEquals("", req2.getValue().getVariant());
138         assertEquals("en-US", req2.getValue().getVoiceName());
139     }
140 
testIsLanguageAvailable()141     public void testIsLanguageAvailable() {
142         IDelegate delegate = LittleMock.mock(IDelegate.class);
143         MockableTextToSpeechService.setMocker(delegate);
144 
145         // Test1: Simple end to end test.
146         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(
147                 delegate).onIsLanguageAvailable("eng", "USA", "");
148 
149         assertEquals(TextToSpeech.LANG_COUNTRY_AVAILABLE, mTts.isLanguageAvailable(Locale.US));
150         LittleMock.verify(delegate, LittleMock.times(1)).onIsLanguageAvailable(
151                 "eng", "USA", "");
152     }
153 
testDefaultLanguage_setsVoiceName()154     public void testDefaultLanguage_setsVoiceName() throws Exception {
155         IDelegate delegate = LittleMock.mock(IDelegate.class);
156         MockableTextToSpeechService.setMocker(delegate);
157         Locale defaultLocale = Locale.getDefault();
158 
159         // ---------------------------------------------------------
160         // Test that default language also sets the default voice
161         // name
162         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
163             when(delegate).onIsLanguageAvailable(
164                 defaultLocale.getISO3Language(),
165                 defaultLocale.getISO3Country().toUpperCase(),
166                 defaultLocale.getVariant());
167         LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
168             when(delegate).onLoadLanguage(
169                 defaultLocale.getISO3Language(),
170                 defaultLocale.getISO3Country(),
171                 defaultLocale.getVariant());
172 
173         blockingCallSpeak("foo bar", delegate);
174         ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor();
175         LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(),
176                 LittleMock.<SynthesisCallback>anyObject());
177 
178         assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage());
179         assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry());
180         assertEquals("", req.getValue().getVariant());
181         assertEquals(defaultLocale.toLanguageTag(), req.getValue().getVoiceName());
182     }
183 
184 
blockingCallSpeak(String speech, IDelegate mock)185     private void blockingCallSpeak(String speech, IDelegate mock) throws
186             InterruptedException {
187         final CountDownLatch latch = new CountDownLatch(1);
188         doCountDown(latch).when(mock).onSynthesizeText(LittleMock.<SynthesisRequest>anyObject(),
189                 LittleMock.<SynthesisCallback>anyObject());
190         mTts.speak(speech, TextToSpeech.QUEUE_ADD, null);
191 
192         awaitCountDown(latch, 5, TimeUnit.SECONDS);
193     }
194 
blockingInitAndVerify(final String engine, int errorCode)195     private void blockingInitAndVerify(final String engine, int errorCode) throws
196             InterruptedException {
197         TextToSpeech.OnInitListener listener = LittleMock.mock(
198                 TextToSpeech.OnInitListener.class);
199 
200         final CountDownLatch latch = new CountDownLatch(1);
201         doCountDown(latch).when(listener).onInit(errorCode);
202 
203         mTts = new TextToSpeech(getInstrumentation().getTargetContext(),
204                 listener, engine, MOCK_PACKAGE, false /* use fallback package */);
205 
206         awaitCountDown(latch, 5, TimeUnit.SECONDS);
207     }
208 
209     public interface CountDownBehaviour extends Behaviour {
210         /** Used to mock methods that return a result. */
andReturn(Object result)211         Behaviour andReturn(Object result);
212     }
213 
doCountDown(final CountDownLatch latch)214     public static CountDownBehaviour doCountDown(final CountDownLatch latch) {
215         return new CountDownBehaviour() {
216             @Override
217             public <T> T when(T mock) {
218                 return LittleMock.doAnswer(new Callable<Void>() {
219                     @Override
220                     public Void call() throws Exception {
221                         latch.countDown();
222                         return null;
223                     }
224                 }).when(mock);
225             }
226 
227             @Override
228             public Behaviour andReturn(final Object result) {
229                 return new Behaviour() {
230                     @Override
231                     public <T> T when(T mock) {
232                         return LittleMock.doAnswer(new Callable<Object>() {
233                             @Override
234                             public Object call() throws Exception {
235                                 latch.countDown();
236                                 return result;
237                             }
238                         }).when(mock);
239                     }
240                 };
241             }
242         };
243     }
244 
245     public static void awaitCountDown(CountDownLatch latch, long timeout, TimeUnit unit)
246             throws InterruptedException {
247         Assert.assertTrue("Waited too long for method call", latch.await(timeout, unit));
248     }
249 }
250