Home | History | Annotate | Download | only in imsphone
      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 import android.telephony.Rlog;
     24 
     25 import com.android.internal.annotations.VisibleForTesting;
     26 
     27 import java.io.IOException;
     28 import java.util.concurrent.CountDownLatch;
     29 
     30 public class ImsRttTextHandler extends Handler {
     31     public interface NetworkWriter {
     32         void write(String s);
     33     }
     34 
     35     private static final String LOG_TAG = "ImsRttTextHandler";
     36     // RTT buffering and sending tuning constants.
     37     // TODO: put this in carrier config?
     38 
     39     // These count Unicode codepoints, not Java char types.
     40     public static final int MAX_CODEPOINTS_PER_SECOND = 30;
     41     // Assuming that we do not exceed the rate limit, this is the maximum time between when a
     42     // piece of text is received and when it is actually sent over the network.
     43     public static final int MAX_BUFFERING_DELAY_MILLIS = 200;
     44     // Assuming that we do not exceed the rate limit, this is the maximum size we will allow
     45     // the buffer to grow to before sending as many as we can.
     46     public static final int MAX_BUFFERED_CHARACTER_COUNT = 5;
     47     private static final int MILLIS_PER_SECOND = 1000;
     48 
     49     // Messages for the handler.
     50     // Initializes the text handler. Should have an RttTextStream set in msg.obj
     51     private static final int INITIALIZE = 1;
     52     // Appends a string to the buffer to send to the network. Should have the string in msg.obj
     53     private static final int APPEND_TO_NETWORK_BUFFER = 2;
     54     // Send a string received from the network to the in-call app. Should have the string in
     55     // msg.obj.
     56     private static final int SEND_TO_INCALL = 3;
     57     // Send as many characters as possible, as constrained by the rate limit. No extra data.
     58     private static final int ATTEMPT_SEND_TO_NETWORK = 4;
     59     // Indicates that N characters were sent a second ago and should be ignored by the rate
     60     // limiter. msg.arg1 should be set to N.
     61     private static final int EXPIRE_SENT_CODEPOINT_COUNT = 5;
     62     // Indicates that the call is over and we should teardown everything we have set up.
     63     private static final int TEARDOWN = 9999;
     64 
     65     private Connection.RttTextStream mRttTextStream;
     66     // For synchronization during testing
     67     private CountDownLatch mReadNotifier;
     68 
     69     private class InCallReaderThread extends Thread {
     70         private final Connection.RttTextStream mReaderThreadRttTextStream;
     71 
     72         public InCallReaderThread(Connection.RttTextStream textStream) {
     73             mReaderThreadRttTextStream = textStream;
     74         }
     75 
     76         @Override
     77         public void run() {
     78             while (true) {
     79                 String charsReceived;
     80                 try {
     81                     charsReceived = mReaderThreadRttTextStream.read();
     82                 } catch (IOException e) {
     83                     Rlog.e(LOG_TAG, "RttReaderThread - IOException encountered " +
     84                             "reading from in-call: %s", e);
     85                     obtainMessage(TEARDOWN).sendToTarget();
     86                     break;
     87                 }
     88                 if (charsReceived == null) {
     89                     if (Thread.currentThread().isInterrupted()) {
     90                         Rlog.i(LOG_TAG, "RttReaderThread - Thread interrupted. Finishing.");
     91                         break;
     92                     }
     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
    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                     sendMessageAtFrontOfQueue(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                     sendMessageAtFrontOfQueue(obtainMessage(ATTEMPT_SEND_TO_NETWORK));
    179                 }
    180                 break;
    181             case TEARDOWN:
    182                 try {
    183                     if (mReaderThread != null) {
    184                         mReaderThread.join(1000);
    185                     }
    186                 } catch (InterruptedException e) {
    187                     // Ignore and assume it'll finish on its own.
    188                 }
    189                 mReaderThread = null;
    190                 mRttTextStream = null;
    191                 break;
    192         }
    193     }
    194 
    195     public ImsRttTextHandler(Looper looper, NetworkWriter networkWriter) {
    196         super(looper);
    197         mNetworkWriter = networkWriter;
    198     }
    199 
    200     public void sendToInCall(String msg) {
    201         obtainMessage(SEND_TO_INCALL, msg).sendToTarget();
    202     }
    203 
    204     public void initialize(Connection.RttTextStream rttTextStream) {
    205         obtainMessage(INITIALIZE, rttTextStream).sendToTarget();
    206     }
    207 
    208     public void tearDown() {
    209         obtainMessage(TEARDOWN).sendToTarget();
    210     }
    211 
    212     @VisibleForTesting
    213     public void setReadNotifier(CountDownLatch latch) {
    214         mReadNotifier = latch;
    215     }
    216 
    217     public String getNetworkBufferText() {
    218         return mBufferedTextToNetwork.toString();
    219     }
    220 }
    221