Implement read/write text stream to RttCall.

This change also:

1. Add simulator support of RTT request during call (always accept at this moment, will add random accept/decline in the future)
2. Fix bugs of putting RTT call in background and back to call

Bug: 67596257
Test: Simulator
PiperOrigin-RevId: 185920527
Change-Id: I51016fa6cf1ccc8a5a21335f9dacf286ae393706
diff --git a/java/com/android/dialer/simulator/impl/RttChatBot.java b/java/com/android/dialer/simulator/impl/RttChatBot.java
index 5a7769f..b2860e3 100644
--- a/java/com/android/dialer/simulator/impl/RttChatBot.java
+++ b/java/com/android/dialer/simulator/impl/RttChatBot.java
@@ -97,7 +97,6 @@
           break;
         case SEND_MESSAGE:
           String message = (String) msg.obj;
-          LogUtil.w("test", "type: %s, to stream: %s", message, rttTextStream);
           try {
             rttTextStream.write(message);
           } catch (IOException e) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
index c832a50..3aa3296 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
@@ -16,9 +16,12 @@
 
 package com.android.dialer.simulator.impl;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
 import android.telecom.Connection;
+import android.telecom.Connection.RttTextStream;
 import android.telecom.ConnectionRequest;
 import android.telecom.VideoProfile;
 import com.android.dialer.common.Assert;
@@ -31,11 +34,14 @@
 import java.util.List;
 
 /** Represents a single phone call on the device. */
+@TargetApi(28)
 public final class SimulatorConnection extends Connection {
   private final List<Listener> listeners = new ArrayList<>();
   private final List<Event> events = new ArrayList<>();
   private final SimulatorConnectionsBank simulatorConnectionsBank;
   private int currentState = STATE_NEW;
+  private RttTextStream rttTextStream;
+  private RttChatBot rttChatBot;
 
   SimulatorConnection(@NonNull Context context, @NonNull ConnectionRequest request) {
     Assert.isNotNull(context);
@@ -54,6 +60,9 @@
             getConnectionCapabilities() | CAPABILITY_SEPARATE_FROM_CONFERENCE);
       }
     }
+    if (BuildCompat.isAtLeastP()) {
+      rttTextStream = request.getRttTextStream();
+    }
     setVideoProvider(new SimulatorVideoProvider(context, this));
     simulatorConnectionsBank = SimulatorComponent.get(context).getSimulatorConnectionsBank();
   }
@@ -66,6 +75,10 @@
     listeners.remove(Assert.isNotNull(listener));
   }
 
+  RttTextStream getRttTextStream() {
+    return rttTextStream;
+  }
+
   @NonNull
   public List<Event> getEvents() {
     return events;
@@ -101,6 +114,11 @@
     LogUtil.enterBlock("SimulatorConnection.onDisconnect");
     simulatorConnectionsBank.remove(this);
     onEvent(new Event(Event.DISCONNECT));
+    rttTextStream = null;
+    if (rttChatBot != null) {
+      rttChatBot.stop();
+      rttChatBot = null;
+    }
   }
 
   @Override
@@ -124,12 +142,21 @@
   @Override
   public void onStartRtt(@NonNull RttTextStream rttTextStream) {
     LogUtil.enterBlock("SimulatorConnection.onStartRtt");
+    if (this.rttTextStream != null || rttChatBot != null) {
+      LogUtil.e("SimulatorConnection.onStartRtt", "rttTextStream or rttChatBot is not null!");
+    }
+    this.rttTextStream = rttTextStream;
+    rttChatBot = new RttChatBot(rttTextStream);
+    rttChatBot.start();
     onEvent(new Event(Event.START_RTT));
   }
 
   @Override
   public void onStopRtt() {
     LogUtil.enterBlock("SimulatorConnection.onStopRtt");
+    rttChatBot.stop();
+    rttChatBot = null;
+    rttTextStream = null;
     onEvent(new Event(Event.STOP_RTT));
   }
 
@@ -159,6 +186,4 @@
   public interface Listener {
     void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event);
   }
-
-
 }
diff --git a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java
index 7b00667..352b9e4 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java
@@ -34,6 +34,7 @@
 
   @NonNull private final Context context;
   @Nullable private String connectionTag;
+  private RttChatBot rttChatBot;
 
   static ActionProvider getActionProvider(@NonNull Context context) {
     return new SimulatorSubMenu(context)
@@ -112,24 +113,29 @@
     switch (event.type) {
       case Event.NONE:
         throw Assert.createIllegalStateFailException();
-      case Event.ANSWER:
-        connection.setActive();
-        break;
       case Event.REJECT:
         connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
         break;
       case Event.HOLD:
         connection.setOnHold();
         break;
+      case Event.ANSWER:
       case Event.UNHOLD:
         connection.setActive();
         break;
       case Event.DISCONNECT:
+        rttChatBot.stop();
         connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
         break;
       case Event.SESSION_MODIFY_REQUEST:
         ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
         break;
+      case Event.STATE_CHANGE:
+        if (Connection.stateToString(Connection.STATE_ACTIVE).equals(event.data2)) {
+          rttChatBot = new RttChatBot(connection.getRttTextStream());
+          rttChatBot.start();
+        }
+        break;
       default:
         LogUtil.i("SimulatorRttCall.onEvent", "unexpected event: " + event.type);
         break;
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
index d51e068..c56afb2 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
@@ -115,7 +115,7 @@
         TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
         callType == CALL_TYPE_VIDEO
             ? getVideoProviderHandle(context)
-            : getSystemPhoneAccountHandle(context));
+            : getSimCallManagerHandle(context));
     if (callType == CALL_TYPE_RTT) {
       outgoingCallExtras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
     }
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
index d4c7ee4..e59cddd 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
@@ -22,6 +22,7 @@
 import android.support.annotation.Nullable;
 import android.support.v7.app.AppCompatActivity;
 import android.telecom.Connection;
+import android.telecom.Connection.RttModifyStatus;
 import android.telecom.DisconnectCause;
 import android.view.ActionProvider;
 import com.android.dialer.common.Assert;
@@ -223,15 +224,13 @@
     switch (event.type) {
       case Event.NONE:
         throw Assert.createIllegalStateFailException();
-      case Event.ANSWER:
-        connection.setActive();
-        break;
       case Event.REJECT:
         connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
         break;
       case Event.HOLD:
         connection.setOnHold();
         break;
+      case Event.ANSWER:
       case Event.UNHOLD:
         connection.setActive();
         break;
@@ -244,6 +243,15 @@
       case Event.SESSION_MODIFY_REQUEST:
         ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
         break;
+      case Event.START_RTT:
+        // TODO(wangqi): Add random accept/decline.
+        boolean accept = true;
+        if (accept) {
+          connection.sendRttInitiationSuccess();
+        } else {
+          connection.sendRttInitiationFailure(RttModifyStatus.SESSION_MODIFY_REQUEST_FAIL);
+        }
+        break;
       default:
         LogUtil.i("SimulatorVoiceCall.onEvent", "unexpected event: " + event.type);
         break;
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 67f5cfe..3fc7f6c 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -182,6 +182,7 @@
       didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
       didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
       didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
+      didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN);
     }
 
     setWindowFlags();
@@ -387,6 +388,7 @@
     out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
     out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
     out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
+    out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen);
 
     super.onSaveInstanceState(out);
     isVisible = false;
@@ -1593,6 +1595,7 @@
     static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
     static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
     static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
+    static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen";
   }
 
   /** Request codes for pending intents. */
diff --git a/java/com/android/incallui/RttCallPresenter.java b/java/com/android/incallui/RttCallPresenter.java
index b90d56b..939c9d0 100644
--- a/java/com/android/incallui/RttCallPresenter.java
+++ b/java/com/android/incallui/RttCallPresenter.java
@@ -16,28 +16,145 @@
 
 package com.android.incallui;
 
-import android.content.Context;
+import android.annotation.TargetApi;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telecom.Call.RttCall;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.incallui.InCallPresenter.InCallState;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall;
 import com.android.incallui.rtt.protocol.RttCallScreen;
 import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
+import java.io.IOException;
 
 /**
  * Logic related to the {@link RttCallScreen} and for managing changes to the RTT calling surfaces
  * based on other user interface events and incoming events.
  */
-public class RttCallPresenter implements RttCallScreenDelegate {
+@TargetApi(28)
+public class RttCallPresenter implements RttCallScreenDelegate, InCallStateListener {
 
-  private Context appContext;
   private RttCallScreen rttCallScreen;
+  private RttCall rttCall;
+  private HandlerThread handlerThread;
+  private RemoteMessageHandler remoteMessageHandler;
 
   @Override
-  public void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen) {
-    this.appContext = context.getApplicationContext();
+  public void initRttCallScreenDelegate(RttCallScreen rttCallScreen) {
     this.rttCallScreen = rttCallScreen;
   }
 
   @Override
-  public void onRttCallScreenUiReady() {}
+  public void onLocalMessage(String message) {
+    if (rttCall == null) {
+      LogUtil.w("RttCallPresenter.onLocalMessage", "Rtt Call is not started yet");
+      return;
+    }
+    remoteMessageHandler.writeMessage(message);
+  }
 
   @Override
-  public void onRttCallScreenUiUnready() {}
+  public void onRttCallScreenUiReady() {
+    LogUtil.enterBlock("RttCallPresenter.onRttCallScreenUiReady");
+    InCallPresenter.getInstance().addListener(this);
+    startListenOnRemoteMessage();
+  }
+
+  @Override
+  public void onRttCallScreenUiUnready() {
+    LogUtil.enterBlock("RttCallPresenter.onRttCallScreenUiUnready");
+    InCallPresenter.getInstance().removeListener(this);
+    stopListenOnRemoteMessage();
+  }
+
+  @Override
+  public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+    LogUtil.enterBlock("RttCallPresenter.onStateChange");
+    if (newState == InCallState.INCALL) {
+      startListenOnRemoteMessage();
+    }
+  }
+
+  private void startListenOnRemoteMessage() {
+    DialerCall call = CallList.getInstance().getActiveCall();
+    if (call == null) {
+      LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "call is active yet");
+      return;
+    }
+    rttCall = call.getRttCall();
+    if (rttCall == null) {
+      LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "RTT Call is not started yet");
+      return;
+    }
+    if (handlerThread != null && handlerThread.isAlive()) {
+      LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "already running");
+      return;
+    }
+    handlerThread = new HandlerThread("RttCallRemoteMessageHandler");
+    handlerThread.start();
+    remoteMessageHandler =
+        new RemoteMessageHandler(handlerThread.getLooper(), rttCall, rttCallScreen);
+    remoteMessageHandler.start();
+  }
+
+  private void stopListenOnRemoteMessage() {
+    if (handlerThread != null && handlerThread.isAlive()) {
+      handlerThread.quit();
+    }
+  }
+
+  private static class RemoteMessageHandler extends Handler {
+    private static final int START = 1;
+    private static final int READ_MESSAGE = 2;
+    private static final int WRITE_MESSAGE = 3;
+
+    private final RttCall rttCall;
+    private final RttCallScreen rttCallScreen;
+
+    RemoteMessageHandler(Looper looper, RttCall rttCall, RttCallScreen rttCallScreen) {
+      super(looper);
+      this.rttCall = rttCall;
+      this.rttCallScreen = rttCallScreen;
+    }
+
+    @Override
+    public void handleMessage(android.os.Message msg) {
+      switch (msg.what) {
+        case START:
+          sendEmptyMessage(READ_MESSAGE);
+          break;
+        case READ_MESSAGE:
+          try {
+            final String message = rttCall.readImmediately();
+            if (message != null) {
+              ThreadUtil.postOnUiThread(() -> rttCallScreen.onRemoteMessage(message));
+            }
+          } catch (IOException e) {
+            LogUtil.e("RttCallPresenter.RemoteMessageHandler.handleMessage", "read message", e);
+          }
+          sendEmptyMessageDelayed(READ_MESSAGE, 200);
+          break;
+        case WRITE_MESSAGE:
+          try {
+            rttCall.write((String) msg.obj);
+          } catch (IOException e) {
+            LogUtil.e("RttCallPresenter.RemoteMessageHandler.handleMessage", "write message", e);
+          }
+          break;
+        default: // fall out
+      }
+    }
+
+    void start() {
+      sendEmptyMessage(START);
+    }
+
+    void writeMessage(String message) {
+      sendMessage(obtainMessage(WRITE_MESSAGE, message));
+    }
+  }
 }
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 3788061..90a0140 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -939,6 +939,14 @@
     }
   }
 
+  @TargetApi(28)
+  public RttCall getRttCall() {
+    if (!isRttCall()) {
+      return null;
+    }
+    return getTelecomCall().getRttCall();
+  }
+
   public boolean hasReceivedVideoUpgradeRequest() {
     return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
   }
@@ -948,7 +956,6 @@
   }
 
   public boolean hasSentRttUpgradeRequest() {
-    // TODO(wangqi): Implement this.
     return false;
   }
 
diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
index 1ea7f31..6983718 100644
--- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java
+++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
@@ -110,7 +110,17 @@
       notifyItemInserted(lastIndexOfLocalMessage);
     } else {
       rttChatMessage.append(newMessage);
-      notifyItemChanged(lastIndexOfLocalMessage);
+      // Clear empty message bubble.
+      if (TextUtils.isEmpty(rttChatMessage.getContent())) {
+        rttMessages.remove(lastIndexOfLocalMessage);
+        notifyItemRemoved(lastIndexOfLocalMessage);
+        if (lastIndexOfRemoteMessage > lastIndexOfLocalMessage) {
+          lastIndexOfRemoteMessage -= 1;
+        }
+        lastIndexOfLocalMessage = -1;
+      } else {
+        notifyItemChanged(lastIndexOfLocalMessage);
+      }
     }
   }
 
diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java
index c7ee2ff..ba99b2b 100644
--- a/java/com/android/incallui/rtt/impl/RttChatFragment.java
+++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java
@@ -132,7 +132,7 @@
         FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class)
             .newRttCallScreenDelegate(this);
 
-    rttCallScreenDelegate.initRttCallScreenDelegate(getContext(), this);
+    rttCallScreenDelegate.initRttCallScreenDelegate(this);
 
     inCallScreenDelegate.onInCallScreenDelegateInit(this);
     inCallScreenDelegate.onInCallScreenReady();
@@ -193,7 +193,24 @@
     if (isClearingInput) {
       return;
     }
-    adapter.addLocalMessage(RttChatMessage.getChangedString(s, start, before, count));
+    String messageToAppend = RttChatMessage.getChangedString(s, start, before, count);
+    if (!TextUtils.isEmpty(messageToAppend)) {
+      adapter.addLocalMessage(messageToAppend);
+      rttCallScreenDelegate.onLocalMessage(messageToAppend);
+    }
+  }
+
+  @Override
+  public void onRemoteMessage(String message) {
+    adapter.addRemoteMessage(message);
+  }
+
+  @Override
+  public void onDestroyView() {
+    super.onDestroyView();
+    LogUtil.enterBlock("RttChatFragment.onDestroyView");
+    inCallButtonUiDelegate.onInCallButtonUiUnready();
+    inCallScreenDelegate.onInCallScreenUnready();
   }
 
   @Override
diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreen.java b/java/com/android/incallui/rtt/protocol/RttCallScreen.java
index afacbae..916dfb8 100644
--- a/java/com/android/incallui/rtt/protocol/RttCallScreen.java
+++ b/java/com/android/incallui/rtt/protocol/RttCallScreen.java
@@ -25,6 +25,8 @@
 
   void onRttScreenStop();
 
+  void onRemoteMessage(String message);
+
   Fragment getRttCallScreenFragment();
 
   String getCallId();
diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java
index e29c43d..8c484a8 100644
--- a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java
+++ b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java
@@ -16,14 +16,14 @@
 
 package com.android.incallui.rtt.protocol;
 
-import android.content.Context;
-
 /** Callbacks from the module out to the container. */
 public interface RttCallScreenDelegate {
 
-  void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen);
+  void initRttCallScreenDelegate(RttCallScreen rttCallScreen);
 
   void onRttCallScreenUiReady();
 
   void onRttCallScreenUiUnready();
+
+  void onLocalMessage(String message);
 }