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 17 package com.android.internal.telephony.imsphone; 18 19 import android.os.Handler; 20 import android.os.Looper; 21 import android.os.Message; 22 import android.telecom.Connection; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.telephony.Rlog; 26 27 import java.io.IOException; 28 import java.nio.channels.ClosedByInterruptException; 29 import java.util.concurrent.CountDownLatch; 30 31 public class ImsRttTextHandler extends Handler { 32 public interface NetworkWriter { write(String s)33 void write(String s); 34 } 35 36 private static final String LOG_TAG = "ImsRttTextHandler"; 37 // RTT buffering and sending tuning constants. 38 // TODO: put this in carrier config? 39 40 // These count Unicode codepoints, not Java char types. 41 public static final int MAX_CODEPOINTS_PER_SECOND = 30; 42 // Assuming that we do not exceed the rate limit, this is the maximum time between when a 43 // piece of text is received and when it is actually sent over the network. 44 public static final int MAX_BUFFERING_DELAY_MILLIS = 200; 45 // Assuming that we do not exceed the rate limit, this is the maximum size we will allow 46 // the buffer to grow to before sending as many as we can. 47 public static final int MAX_BUFFERED_CHARACTER_COUNT = 5; 48 private static final int MILLIS_PER_SECOND = 1000; 49 50 // Messages for the handler. 51 // Initializes the text handler. Should have an RttTextStream set in msg.obj 52 private static final int INITIALIZE = 1; 53 // Appends a string to the buffer to send to the network. Should have the string in msg.obj 54 private static final int APPEND_TO_NETWORK_BUFFER = 2; 55 // Send a string received from the network to the in-call app. Should have the string in 56 // msg.obj. 57 private static final int SEND_TO_INCALL = 3; 58 // Send as many characters as possible, as constrained by the rate limit. No extra data. 59 private static final int ATTEMPT_SEND_TO_NETWORK = 4; 60 // Indicates that N characters were sent a second ago and should be ignored by the rate 61 // limiter. msg.arg1 should be set to N. 62 private static final int EXPIRE_SENT_CODEPOINT_COUNT = 5; 63 // Indicates that the call is over and we should teardown everything we have set up. 64 private static final int TEARDOWN = 9999; 65 66 private Connection.RttTextStream mRttTextStream; 67 // For synchronization during testing 68 private CountDownLatch mReadNotifier; 69 70 private class InCallReaderThread extends Thread { 71 private final Connection.RttTextStream mReaderThreadRttTextStream; 72 InCallReaderThread(Connection.RttTextStream textStream)73 public InCallReaderThread(Connection.RttTextStream textStream) { 74 mReaderThreadRttTextStream = textStream; 75 } 76 77 @Override run()78 public void run() { 79 while (true) { 80 String charsReceived; 81 try { 82 charsReceived = mReaderThreadRttTextStream.read(); 83 } catch (ClosedByInterruptException e) { 84 Rlog.i(LOG_TAG, "RttReaderThread - Thread interrupted. Finishing."); 85 break; 86 } catch (IOException e) { 87 Rlog.e(LOG_TAG, "RttReaderThread - IOException encountered " + 88 "reading from in-call: ", e); 89 obtainMessage(TEARDOWN).sendToTarget(); 90 break; 91 } 92 if (charsReceived == null) { 93 Rlog.e(LOG_TAG, "RttReaderThread - Stream closed unexpectedly. Attempt to " + 94 "reinitialize."); 95 obtainMessage(TEARDOWN).sendToTarget(); 96 break; 97 } 98 if (charsReceived.length() == 0) { 99 continue; 100 } 101 obtainMessage(APPEND_TO_NETWORK_BUFFER, charsReceived) 102 .sendToTarget(); 103 if (mReadNotifier != null) { 104 mReadNotifier.countDown(); 105 } 106 } 107 } 108 } 109 110 private int mCodepointsAvailableForTransmission = MAX_CODEPOINTS_PER_SECOND; 111 private StringBuffer mBufferedTextToNetwork = new StringBuffer(); 112 private InCallReaderThread mReaderThread; 113 // This is only ever used when the pipes fail and we have to re-setup. Messages received 114 // from the network are buffered here until Telecom gets back to us with the new pipes. 115 private StringBuffer mBufferedTextToIncall = new StringBuffer(); 116 private final NetworkWriter mNetworkWriter; 117 118 @Override handleMessage(Message msg)119 public void handleMessage(Message msg) { 120 switch (msg.what) { 121 case INITIALIZE: 122 if (mRttTextStream != null || mReaderThread != null) { 123 Rlog.e(LOG_TAG, "RTT text stream already initialized. Ignoring."); 124 return; 125 } 126 mRttTextStream = (Connection.RttTextStream) msg.obj; 127 mReaderThread = new InCallReaderThread(mRttTextStream); 128 mReaderThread.start(); 129 break; 130 case SEND_TO_INCALL: 131 String messageToIncall = (String) msg.obj; 132 try { 133 mRttTextStream.write(messageToIncall); 134 } catch (IOException e) { 135 Rlog.e(LOG_TAG, "IOException encountered writing to in-call: %s", e); 136 obtainMessage(TEARDOWN).sendToTarget(); 137 mBufferedTextToIncall.append(messageToIncall); 138 } 139 break; 140 case APPEND_TO_NETWORK_BUFFER: 141 // First, append the text-to-send to the string buffer 142 mBufferedTextToNetwork.append((String) msg.obj); 143 // Check to see how many codepoints we have buffered. If we have more than 5, 144 // send immediately, otherwise, wait until a timeout happens. 145 int numCodepointsBuffered = mBufferedTextToNetwork 146 .codePointCount(0, mBufferedTextToNetwork.length()); 147 if (numCodepointsBuffered >= MAX_BUFFERED_CHARACTER_COUNT) { 148 sendMessage(obtainMessage(ATTEMPT_SEND_TO_NETWORK)); 149 } else { 150 sendEmptyMessageDelayed( 151 ATTEMPT_SEND_TO_NETWORK, MAX_BUFFERING_DELAY_MILLIS); 152 } 153 break; 154 case ATTEMPT_SEND_TO_NETWORK: 155 // Check to see how many codepoints we can send, and send that many. 156 int numCodePointsAvailableInBuffer = mBufferedTextToNetwork.codePointCount(0, 157 mBufferedTextToNetwork.length()); 158 int numCodePointsSent = Math.min(numCodePointsAvailableInBuffer, 159 mCodepointsAvailableForTransmission); 160 if (numCodePointsSent == 0) { 161 break; 162 } 163 int endSendIndex = mBufferedTextToNetwork.offsetByCodePoints(0, 164 numCodePointsSent); 165 166 String stringToSend = mBufferedTextToNetwork.substring(0, endSendIndex); 167 168 mBufferedTextToNetwork.delete(0, endSendIndex); 169 mNetworkWriter.write(stringToSend); 170 mCodepointsAvailableForTransmission -= numCodePointsSent; 171 sendMessageDelayed( 172 obtainMessage(EXPIRE_SENT_CODEPOINT_COUNT, numCodePointsSent, 0), 173 MILLIS_PER_SECOND); 174 break; 175 case EXPIRE_SENT_CODEPOINT_COUNT: 176 mCodepointsAvailableForTransmission += msg.arg1; 177 if (mCodepointsAvailableForTransmission > 0) { 178 sendMessage(obtainMessage(ATTEMPT_SEND_TO_NETWORK)); 179 } 180 break; 181 case TEARDOWN: 182 try { 183 if (mReaderThread != null) { 184 mReaderThread.interrupt(); 185 mReaderThread.join(1000); 186 } 187 } catch (InterruptedException e) { 188 // Ignore and assume it'll finish on its own. 189 } 190 mReaderThread = null; 191 mRttTextStream = null; 192 break; 193 } 194 } 195 ImsRttTextHandler(Looper looper, NetworkWriter networkWriter)196 public ImsRttTextHandler(Looper looper, NetworkWriter networkWriter) { 197 super(looper); 198 mNetworkWriter = networkWriter; 199 } 200 sendToInCall(String msg)201 public void sendToInCall(String msg) { 202 obtainMessage(SEND_TO_INCALL, msg).sendToTarget(); 203 } 204 initialize(Connection.RttTextStream rttTextStream)205 public void initialize(Connection.RttTextStream rttTextStream) { 206 Rlog.i(LOG_TAG, "Initializing: " + this); 207 obtainMessage(INITIALIZE, rttTextStream).sendToTarget(); 208 } 209 tearDown()210 public void tearDown() { 211 obtainMessage(TEARDOWN).sendToTarget(); 212 } 213 214 @VisibleForTesting setReadNotifier(CountDownLatch latch)215 public void setReadNotifier(CountDownLatch latch) { 216 mReadNotifier = latch; 217 } 218 getNetworkBufferText()219 public String getNetworkBufferText() { 220 return mBufferedTextToNetwork.toString(); 221 } 222 } 223