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
17package com.android.speech.tts;
18
19import android.speech.tts.SynthesisCallback;
20import android.speech.tts.SynthesisRequest;
21import android.speech.tts.TextToSpeech;
22import android.test.InstrumentationTestCase;
23
24import com.android.speech.tts.MockableTextToSpeechService.IDelegate;
25import com.google.testing.littlemock.ArgumentCaptor;
26import com.google.testing.littlemock.Behaviour;
27import com.google.testing.littlemock.LittleMock;
28import junit.framework.Assert;
29
30import java.util.Locale;
31import java.util.concurrent.Callable;
32import java.util.concurrent.CountDownLatch;
33import java.util.concurrent.TimeUnit;
34
35public 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
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
59    public void tearDown() {
60        if (mTts != null) {
61            mTts.shutdown();
62        }
63    }
64
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
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
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
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
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
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
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
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. */
211        Behaviour andReturn(Object result);
212    }
213
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