1package com.android.car.messenger.tts;
2
3import android.speech.tts.TextToSpeech;
4
5import com.android.car.messenger.TestConfig;
6
7import org.junit.Assert;
8import org.junit.Before;
9import org.junit.Test;
10import org.junit.runner.RunWith;
11import org.robolectric.Robolectric;
12import org.robolectric.RobolectricTestRunner;
13import org.robolectric.RuntimeEnvironment;
14import org.robolectric.annotation.Config;
15import org.robolectric.util.Scheduler;
16
17import java.util.Arrays;
18import java.util.Collections;
19
20@RunWith(RobolectricTestRunner.class)
21@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
22public class TTSHelperTest {
23    private static final long SHUTDOWN_DELAY_MILLIS = 10;
24
25    private FakeTTSEngine mFakeTTS;
26    private TTSHelper mTTSHelper;
27
28    @Before
29    public void setup() {
30        mFakeTTS = new FakeTTSEngine();
31        mTTSHelper = new TTSHelper(RuntimeEnvironment.application, mFakeTTS, SHUTDOWN_DELAY_MILLIS);
32    }
33
34    @Test
35    public void testSpeakBeforeInit() {
36        // Should get queued since engine not initialized.
37        final RecordingTTSListener recorder1 = new RecordingTTSListener();
38        mTTSHelper.requestPlay(Collections.singletonList("hello"), recorder1);
39        // Will squash previous request.
40        final RecordingTTSListener recorder2 = new RecordingTTSListener();
41        mTTSHelper.requestPlay(Collections.singletonList("world"), recorder2);
42        Assert.assertFalse(recorder1.wasStarted());
43        Assert.assertTrue(recorder1.wasStoppedWithSuccess());
44
45        // Finish initialization.
46        mFakeTTS.mOnInitListener.onInit(TextToSpeech.SUCCESS);
47
48        // The queued request should have been passed for playout.
49        FakeTTSEngine.Request lastRequest = mFakeTTS.mRequests.getLast();
50        Assert.assertEquals("world", lastRequest.mText);
51        Assert.assertEquals(TextToSpeech.QUEUE_ADD, lastRequest.mQueueMode);
52
53        mFakeTTS.startRequest(lastRequest.mUtteranceId);
54        Assert.assertTrue(recorder2.wasStarted());
55        mFakeTTS.finishRequest(lastRequest.mUtteranceId);
56        Assert.assertTrue(recorder2.wasStoppedWithSuccess());
57    }
58
59    @Test
60    public void testSpeakInterruptedByNext() {
61        // First request made and starts playing.
62        final RecordingTTSListener recorder1 = new RecordingTTSListener();
63        mTTSHelper.requestPlay(Collections.singletonList("hello"), recorder1);
64        // Finish initialization.
65        mFakeTTS.mOnInitListener.onInit(TextToSpeech.SUCCESS);
66        final FakeTTSEngine.Request request1 = mFakeTTS.mRequests.getLast();
67        mFakeTTS.startRequest(request1.mUtteranceId);
68        Assert.assertTrue(recorder1.wasStarted());
69
70        // Second request made. It will flush first one.
71        final RecordingTTSListener recorder2 = new RecordingTTSListener();
72        mTTSHelper.requestPlay(Collections.singletonList("world"), recorder2);
73        final FakeTTSEngine.Request request2 = mFakeTTS.mRequests.getLast();
74        mFakeTTS.interruptRequest(request1.mUtteranceId, true /* interrupted */);
75        Assert.assertTrue(recorder1.wasStoppedWithSuccess());
76        mFakeTTS.startRequest(request2.mUtteranceId);
77        Assert.assertTrue(recorder2.wasStarted());
78        mFakeTTS.finishRequest(request2.mUtteranceId);
79        Assert.assertTrue(recorder2.wasStoppedWithSuccess());
80    }
81
82    @Test
83    public void testKeepAliveAndAutoShutdown() {
84        // Request made and starts playing.
85        final RecordingTTSListener recorder1 = new RecordingTTSListener();
86        mTTSHelper.requestPlay(Collections.singletonList("hello"), recorder1);
87        // Finish initialization.
88        mFakeTTS.mOnInitListener.onInit(TextToSpeech.SUCCESS);
89        final FakeTTSEngine.Request request1 = mFakeTTS.mRequests.getLast();
90        mFakeTTS.startRequest(request1.mUtteranceId);
91        Assert.assertTrue(recorder1.wasStarted());
92        mFakeTTS.finishRequest(request1.mUtteranceId);
93        Assert.assertTrue(recorder1.wasStoppedWithSuccess());
94
95        Scheduler scheduler = Robolectric.getForegroundThreadScheduler();
96        scheduler.advanceBy(SHUTDOWN_DELAY_MILLIS / 2);
97        Assert.assertTrue(mFakeTTS.isInitialized());
98
99        // Queue another request, forces keep-alive.
100        final RecordingTTSListener recorder2 = new RecordingTTSListener();
101        mTTSHelper.requestPlay(Collections.singletonList("world"), recorder2);
102        final FakeTTSEngine.Request request2 = mFakeTTS.mRequests.getLast();
103        mFakeTTS.failRequest(request2.mUtteranceId, -2 /* errorCode */);
104        Assert.assertTrue(recorder2.wasStoppedWithError());
105        Assert.assertTrue(mFakeTTS.isInitialized());
106
107        scheduler.advanceBy(SHUTDOWN_DELAY_MILLIS);
108        Assert.assertFalse(mFakeTTS.isInitialized());
109    }
110
111    @Test
112    public void testBatchPlayout() {
113        // Request made and starts playing.
114        final RecordingTTSListener recorder1 = new RecordingTTSListener();
115        mTTSHelper.requestPlay(Arrays.asList("hello", "world"), recorder1);
116        // Finish initialization.
117        mFakeTTS.mOnInitListener.onInit(TextToSpeech.SUCCESS);
118        // Two requests should be queued from batch.
119        Assert.assertEquals(2, mFakeTTS.mRequests.size());
120        final FakeTTSEngine.Request request1 = mFakeTTS.mRequests.getFirst();
121        final FakeTTSEngine.Request request2 = mFakeTTS.mRequests.getLast();
122        mFakeTTS.startRequest(request1.mUtteranceId);
123        Assert.assertTrue(recorder1.wasStarted());
124        mFakeTTS.finishRequest(request1.mUtteranceId);
125        Assert.assertFalse(recorder1.wasStoppedWithSuccess());
126        mFakeTTS.startRequest(request2.mUtteranceId);
127        Assert.assertTrue(recorder1.wasStarted());
128        mFakeTTS.finishRequest(request2.mUtteranceId);
129        Assert.assertTrue(recorder1.wasStoppedWithSuccess());
130    }
131
132    private static class RecordingTTSListener implements TTSHelper.Listener {
133        private boolean mStarted = false;
134        private boolean mStopped = false;
135        private boolean mError = false;
136
137        boolean wasStarted() {
138            return mStarted;
139        }
140
141        boolean wasStoppedWithSuccess() {
142            return mStopped && !mError;
143        }
144
145        boolean wasStoppedWithError() {
146            return mStopped && mError;
147        }
148
149        @Override
150        public void onTTSStarted() {
151            mStarted = true;
152        }
153
154        @Override
155        public void onTTSStopped(boolean error) {
156            mStopped = true;
157            mError = error;
158        }
159    }
160}