1/*
2 * Copyright (C) 2017 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.internal.telephony.imsphone;
18
19import android.os.HandlerThread;
20import android.os.ParcelFileDescriptor;
21import android.support.test.filters.FlakyTest;
22import android.telecom.Connection;
23
24import com.android.internal.telephony.TelephonyTest;
25
26import org.junit.After;
27import org.junit.Assert;
28import org.junit.Before;
29import org.junit.ComparisonFailure;
30import org.junit.Test;
31
32import java.io.IOException;
33import java.io.InputStreamReader;
34import java.io.OutputStreamWriter;
35import java.util.concurrent.CountDownLatch;
36import java.util.concurrent.TimeUnit;
37
38public class ImsRttTextHandlerTest extends TelephonyTest {
39    private static final int TEST_TIMEOUT = 1000;
40    private static final int READ_BUFFER_SIZE = 1000;
41    private static final String LONG_TEXT = "No Soldier shall, in time of peace be quartered in " +
42            "any house, without the consent of the Owner, nor in time of war, but in a manner to " +
43            "be prescribed by law.";
44
45    char[] buffer = new char[READ_BUFFER_SIZE];
46
47    public class MockNetworkWriter implements ImsRttTextHandler.NetworkWriter {
48        private String totalWritten = "";
49        private int numWrites = 0;
50
51        @Override
52        public synchronized void write(String s) {
53            totalWritten += s;
54            numWrites += 1;
55        }
56
57        public synchronized void reset() {
58            totalWritten = "";
59            numWrites = 0;
60        }
61
62        public synchronized String getContents() {
63            return totalWritten;
64        }
65
66        public synchronized int getNumWrites() {
67            return numWrites;
68        }
69    }
70
71    Connection.RttTextStream mRttTextStream;
72    MockNetworkWriter mNetworkWriter = new MockNetworkWriter();
73    ImsRttTextHandler mRttTextHandler;
74    HandlerThread mHandlerThread;
75
76    OutputStreamWriter mPipeToHandler;
77    InputStreamReader mPipeFromHandler;
78    InputStreamReader mHandlerSideOfPipeToHandler;
79
80    @Before
81    public void setUp() throws Exception {
82        super.setUp(getClass().getSimpleName());
83        mNetworkWriter.reset();
84        mHandlerThread = new HandlerThread("TestImsRttTextHandler");
85        mHandlerThread.start();
86        mRttTextHandler = new ImsRttTextHandler(mHandlerThread.getLooper(), mNetworkWriter);
87
88        // Construct some pipes to use
89        ParcelFileDescriptor[] toTextHandler = ParcelFileDescriptor.createReliablePipe();
90        ParcelFileDescriptor[] fromTextHandler = ParcelFileDescriptor.createReliablePipe();
91        mRttTextStream = new Connection.RttTextStream(fromTextHandler[1],toTextHandler[0]);
92
93        mRttTextHandler.initialize(mRttTextStream);
94
95        mPipeFromHandler = new InputStreamReader(
96                new ParcelFileDescriptor.AutoCloseInputStream(fromTextHandler[0]));
97        mPipeToHandler = new OutputStreamWriter(
98                new ParcelFileDescriptor.AutoCloseOutputStream(toTextHandler[1]));
99        mHandlerSideOfPipeToHandler = new InputStreamReader(
100                new ParcelFileDescriptor.AutoCloseInputStream(toTextHandler[1]));
101    }
102
103    /**
104     * Test that the text handler won't send characters before a timeout or enough characters
105     * have accumulated.
106     */
107    @Test
108    public void testProperCharacterBuffering() throws Exception {
109        // Send four characters
110        mPipeToHandler.write("abcd");
111        mPipeToHandler.flush();
112        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
113        // make sure at it hasn't been sent.
114        Assert.assertEquals("", mNetworkWriter.getContents());
115        // Wait for 300ms
116        waitForHandlerActionDelayed(mRttTextHandler, TEST_TIMEOUT,
117                ImsRttTextHandler.MAX_BUFFERING_DELAY_MILLIS + 100);
118        // make sure that it has been sent and check that it's correct
119        Assert.assertEquals("abcd", mNetworkWriter.getContents());
120    }
121
122    /**
123     * Test that the text handler sends after enough characters have been sent from in-call
124     * @throws Exception
125     */
126    @FlakyTest
127    @Test
128    public void testSendAfterEnoughChars() throws Exception {
129        // Register a read notifier
130        CountDownLatch readNotifier = new CountDownLatch(1);
131        mRttTextHandler.setReadNotifier(readNotifier);
132        // Send four characters
133        mPipeToHandler.write("abcd");
134        mPipeToHandler.flush();
135        // Wait for the stream to consume the characters
136        readNotifier.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
137        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
138        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
139        // make sure at it hasn't been sent.
140        Assert.assertEquals("", mNetworkWriter.getContents());
141
142        // Send the second part
143        Thread.sleep(10);
144        // Register a read notifier
145        readNotifier = new CountDownLatch(1);
146        mRttTextHandler.setReadNotifier(readNotifier);
147        // Send four more characters
148        mPipeToHandler.write("efgh");
149        mPipeToHandler.flush();
150        // Wait for the stream to consume the characters
151        boolean res = readNotifier.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
152        // Wait for the handler to write to the mock network writer
153        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
154        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
155        // make sure that all characters were sent.
156        try {
157            Assert.assertEquals("abcdefgh", mNetworkWriter.getContents());
158        } catch (ComparisonFailure e) {
159            throw new ComparisonFailure(e.getMessage()
160                    + ", network buffer=" + mRttTextHandler.getNetworkBufferText()
161                    + ", res=" + res,
162                    e.getExpected(), e.getActual());
163        }
164    }
165
166    /**
167     * Test that the text handler sends its characters as a batch after enough of them have been
168     * buffered.
169     * @throws Exception
170     */
171    @Test
172    public void testBufferedCharactersSentAsBatch() throws Exception {
173        // Send 5 characters, one at a time, pausing for 10ms between each one.
174        char[] characters = new char[] {'a', 'b', 'c', 'd', 'e'};
175        for (char c : characters) {
176            mPipeToHandler.write(String.valueOf(c));
177            mPipeToHandler.flush();
178            Thread.sleep(10);
179        }
180        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
181        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
182
183        // Make sure that the ordering is correct and that there was only one write
184        Assert.assertEquals("abcde", mNetworkWriter.getContents());
185        Assert.assertEquals(1, mNetworkWriter.getNumWrites());
186    }
187
188    @Test
189    public void testProperThrottling() throws Exception {
190        // Send a lot of characters in rapid succession, 3 at a time
191        char[] characters = LONG_TEXT.toCharArray();
192        for (int i = 0; i < characters.length; i += 3) {
193            String toSend = new String(characters, i, Math.min(3, characters.length - i));
194            mPipeToHandler.write(toSend);
195            mPipeToHandler.flush();
196            Thread.sleep(10);
197        }
198        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
199        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
200
201        // Wait one second and see how many characters are sent in that time.
202        int numCharsSoFar = mNetworkWriter.getContents().length();
203        Thread.sleep(1000);
204        int numCharsInOneSec = mNetworkWriter.getContents().length() - numCharsSoFar;
205        Assert.assertTrue(numCharsInOneSec <= ImsRttTextHandler.MAX_CODEPOINTS_PER_SECOND);
206
207        // Wait 5 seconds for all the chars to make it through
208        Thread.sleep(5000);
209        Assert.assertEquals(LONG_TEXT, mNetworkWriter.getContents());
210    }
211
212    @Test
213    public void testProperTransmissionFromNetworkToInCall() throws Exception {
214        // Make sure that nothing is in the pipe from the network to incall (us)
215        Assert.assertFalse(mPipeFromHandler.ready());
216        // Send a chunk of text
217        mRttTextHandler.sendToInCall(LONG_TEXT);
218        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
219        // Make sure we get it immediately
220        Assert.assertEquals(LONG_TEXT, readAll(mPipeFromHandler));
221    }
222
223    @After
224    public void tearDown() throws Exception {
225        mPipeFromHandler.close();
226        mPipeToHandler.close();
227        mRttTextHandler.tearDown();
228        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
229        mHandlerThread.quit();
230        super.tearDown();
231    }
232
233    private String readAll(InputStreamReader inputStreamReader) throws IOException {
234        if (!inputStreamReader.ready()) {
235            return null;
236        }
237        int len = inputStreamReader.read(buffer, 0, READ_BUFFER_SIZE);
238        return new String(buffer, 0, len);
239    }
240}
241