Allow user to delete previous bubbles.

After this change, user will be able to delete text in previous finished bubble. It will also correctly handle deletion from remote.

Bug: 67596257
Test: RttChatMessageTest
PiperOrigin-RevId: 190122728
Change-Id: Ifebcbe874e5f03857d109b58e758e53f408e7e44
diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
index 7e2b571..8d924c9 100644
--- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java
+++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
@@ -37,13 +37,11 @@
   }
 
   private static final String KEY_MESSAGE_DATA = "key_message_data";
-  private static final String KEY_LAST_REMOTE_MESSAGE = "key_last_remote_message";
   private static final String KEY_LAST_LOCAL_MESSAGE = "key_last_local_message";
 
   private final Context context;
   private final List<RttChatMessage> rttMessages;
   private int lastIndexOfLocalMessage = -1;
-  private int lastIndexOfRemoteMessage = -1;
   private final MessageListener messageListener;
 
   RttChatAdapter(Context context, MessageListener listener, @Nullable Bundle savedInstanceState) {
@@ -53,7 +51,6 @@
       rttMessages = new ArrayList<>();
     } else {
       rttMessages = savedInstanceState.getParcelableArrayList(KEY_MESSAGE_DATA);
-      lastIndexOfRemoteMessage = savedInstanceState.getInt(KEY_LAST_REMOTE_MESSAGE);
       lastIndexOfLocalMessage = savedInstanceState.getInt(KEY_LAST_LOCAL_MESSAGE);
     }
   }
@@ -84,33 +81,6 @@
     return rttMessages.size();
   }
 
-  private void updateCurrentRemoteMessage(String newText) {
-    RttChatMessage rttChatMessage = null;
-    if (lastIndexOfRemoteMessage >= 0) {
-      rttChatMessage = rttMessages.get(lastIndexOfRemoteMessage);
-    }
-    List<RttChatMessage> newMessages =
-        RttChatMessage.getRemoteRttChatMessage(rttChatMessage, newText);
-
-    if (rttChatMessage == null) {
-      lastIndexOfRemoteMessage = rttMessages.size();
-      rttMessages.add(lastIndexOfRemoteMessage, newMessages.get(0));
-      rttMessages.addAll(newMessages.subList(1, newMessages.size()));
-      notifyItemRangeInserted(lastIndexOfRemoteMessage, newMessages.size());
-      lastIndexOfRemoteMessage = rttMessages.size() - 1;
-    } else {
-      rttMessages.set(lastIndexOfRemoteMessage, newMessages.get(0));
-      int lastIndex = rttMessages.size();
-      rttMessages.addAll(newMessages.subList(1, newMessages.size()));
-
-      notifyItemChanged(lastIndexOfRemoteMessage);
-      notifyItemRangeInserted(lastIndex, newMessages.size());
-    }
-    if (rttMessages.get(lastIndexOfRemoteMessage).isFinished()) {
-      lastIndexOfRemoteMessage = -1;
-    }
-  }
-
   private void updateCurrentLocalMessage(String newMessage) {
     RttChatMessage rttChatMessage = null;
     if (lastIndexOfLocalMessage >= 0) {
@@ -128,9 +98,6 @@
       if (TextUtils.isEmpty(rttChatMessage.getContent())) {
         rttMessages.remove(lastIndexOfLocalMessage);
         notifyItemRemoved(lastIndexOfLocalMessage);
-        if (lastIndexOfRemoteMessage > lastIndexOfLocalMessage) {
-          lastIndexOfRemoteMessage -= 1;
-        }
         lastIndexOfLocalMessage = -1;
       } else {
         notifyItemChanged(lastIndexOfLocalMessage);
@@ -138,8 +105,13 @@
     }
   }
 
+  private void updateCurrentRemoteMessage(String newMessage) {
+    RttChatMessage.updateRemoteRttChatMessage(rttMessages, newMessage);
+    lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages);
+    notifyDataSetChanged();
+  }
+
   void addLocalMessage(String message) {
-    LogUtil.enterBlock("RttChatAdapater.addLocalMessage");
     updateCurrentLocalMessage(message);
     if (messageListener != null) {
       messageListener.newMessageAdded();
@@ -166,7 +138,6 @@
   }
 
   void addRemoteMessage(String message) {
-    LogUtil.enterBlock("RttChatAdapater.addRemoteMessage");
     if (TextUtils.isEmpty(message)) {
       return;
     }
@@ -176,9 +147,24 @@
     }
   }
 
+  /**
+   * Retrieve last local message and update the index. This is used when deleting to previous
+   * message bubble.
+   */
+  @Nullable
+  String retrieveLastLocalMessage() {
+    lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages);
+    if (lastIndexOfLocalMessage >= 0) {
+      RttChatMessage rttChatMessage = rttMessages.get(lastIndexOfLocalMessage);
+      rttChatMessage.unfinish();
+      return rttChatMessage.getContent();
+    } else {
+      return null;
+    }
+  }
+
   void onSaveInstanceState(@NonNull Bundle bundle) {
     bundle.putParcelableArrayList(KEY_MESSAGE_DATA, (ArrayList<RttChatMessage>) rttMessages);
-    bundle.putInt(KEY_LAST_REMOTE_MESSAGE, lastIndexOfRemoteMessage);
     bundle.putInt(KEY_LAST_LOCAL_MESSAGE, lastIndexOfLocalMessage);
   }
 }
diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java
index 90bf199..e14ee9b 100644
--- a/java/com/android/incallui/rtt/impl/RttChatFragment.java
+++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java
@@ -164,6 +164,26 @@
     editText = view.findViewById(R.id.rtt_chat_input);
     editText.setOnEditorActionListener(this);
     editText.addTextChangedListener(this);
+
+    editText.setOnKeyListener(
+        (v, keyCode, event) -> {
+          // This is only triggered when input method doesn't handle delete key, which means the
+          // current
+          // input box is empty.
+          if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
+            String lastMessage = adapter.retrieveLastLocalMessage();
+            if (lastMessage != null) {
+              isClearingInput = true;
+              editText.setText(lastMessage);
+              editText.setSelection(lastMessage.length());
+              isClearingInput = false;
+              rttCallScreenDelegate.onLocalMessage("\b");
+              return true;
+            }
+            return false;
+          }
+          return false;
+        });
     recyclerView = view.findViewById(R.id.rtt_recycler_view);
     LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
     layoutManager.setStackFromEnd(true);
@@ -207,7 +227,9 @@
   @Override
   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
     if (actionId == EditorInfo.IME_ACTION_SEND) {
-      submitButton.performClick();
+      if (!TextUtils.isEmpty(editText.getText())) {
+        submitButton.performClick();
+      }
       return true;
     }
     return false;
diff --git a/java/com/android/incallui/rtt/impl/RttChatMessage.java b/java/com/android/incallui/rtt/impl/RttChatMessage.java
index fd83fb8..cbc53ef 100644
--- a/java/com/android/incallui/rtt/impl/RttChatMessage.java
+++ b/java/com/android/incallui/rtt/impl/RttChatMessage.java
@@ -19,10 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import com.android.dialer.common.Assert;
 import com.android.incallui.rtt.protocol.Constants;
 import com.google.common.base.Splitter;
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
@@ -44,13 +43,17 @@
     isFinished = true;
   }
 
+  void unfinish() {
+    isFinished = false;
+  }
+
   public void append(String text) {
     for (int i = 0; i < text.length(); i++) {
       char c = text.charAt(i);
-      if (c != '\b') {
-        content.append(c);
-      } else if (content.length() > 0) {
+      if (c == '\b' && content.length() > 0 && content.charAt(content.length() - 1) != '\b') {
         content.deleteCharAt(content.length() - 1);
+      } else {
+        content.append(c);
       }
     }
   }
@@ -87,39 +90,92 @@
     return modify.toString();
   }
 
-  /** Convert remote input text into an array of {@code RttChatMessage}. */
-  static List<RttChatMessage> getRemoteRttChatMessage(
-      @Nullable RttChatMessage currentMessage, @NonNull String text) {
+  /** Update list of {@code RttChatMessage} based on given remote text. */
+  static void updateRemoteRttChatMessage(List<RttChatMessage> messageList, @NonNull String text) {
+    Assert.isNotNull(messageList);
     Iterator<String> splitText = SPLITTER.split(text).iterator();
-    List<RttChatMessage> messageList = new ArrayList<>();
-
-    String firstMessageContent = splitText.next();
-    RttChatMessage firstMessage = currentMessage;
-    if (firstMessage == null) {
-      firstMessage = new RttChatMessage();
-      firstMessage.isRemote = true;
-    }
-    firstMessage.append(firstMessageContent);
-    if (splitText.hasNext() || text.endsWith(Constants.BUBBLE_BREAKER)) {
-      firstMessage.finish();
-    }
-    messageList.add(firstMessage);
 
     while (splitText.hasNext()) {
       String singleMessageContent = splitText.next();
-      if (singleMessageContent.isEmpty()) {
-        continue;
+      RttChatMessage message;
+      int index = getLastIndexUnfinishedRemoteMessage(messageList);
+      if (index < 0) {
+        message = new RttChatMessage();
+        message.append(singleMessageContent);
+        message.isRemote = true;
+        if (splitText.hasNext()) {
+          message.finish();
+        }
+        if (message.content.length() != 0) {
+          messageList.add(message);
+        }
+      } else {
+        message = messageList.get(index);
+        message.append(singleMessageContent);
+        if (splitText.hasNext()) {
+          message.finish();
+        }
+        if (message.content.length() == 0) {
+          messageList.remove(index);
+        }
       }
-      RttChatMessage message = new RttChatMessage();
-      message.append(singleMessageContent);
-      message.isRemote = true;
-      if (splitText.hasNext()) {
-        message.finish();
+      StringBuilder content = message.content;
+      // Delete previous messages.
+      while (content.length() > 0 && content.charAt(0) == '\b') {
+        messageList.remove(message);
+        content.delete(0, 1);
+        int previous = getLastIndexRemoteMessage(messageList);
+        // There are more backspaces than existing characters.
+        if (previous < 0) {
+          while (content.length() > 0 && content.charAt(0) == '\b') {
+            content.deleteCharAt(0);
+          }
+          // Add message if there are still characters after backspaces.
+          if (content.length() > 0) {
+            message = new RttChatMessage();
+            message.append(content.toString());
+            message.isRemote = true;
+            if (splitText.hasNext()) {
+              message.finish();
+            }
+            messageList.add(message);
+          }
+          break;
+        }
+        message = messageList.get(previous);
+        message.unfinish();
+        message.append(content.toString());
+        content = message.content;
       }
-      messageList.add(message);
     }
+    if (text.endsWith(Constants.BUBBLE_BREAKER)) {
+      int lastIndexRemoteMessage = getLastIndexRemoteMessage(messageList);
+      messageList.get(lastIndexRemoteMessage).finish();
+    }
+  }
 
-    return messageList;
+  private static int getLastIndexUnfinishedRemoteMessage(List<RttChatMessage> messageList) {
+    int i = messageList.size() - 1;
+    while (i >= 0 && (!messageList.get(i).isRemote || messageList.get(i).isFinished)) {
+      i--;
+    }
+    return i;
+  }
+
+  private static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) {
+    int i = messageList.size() - 1;
+    while (i >= 0 && !messageList.get(i).isRemote) {
+      i--;
+    }
+    return i;
+  }
+
+  static int getLastIndexLocalMessage(List<RttChatMessage> messageList) {
+    int i = messageList.size() - 1;
+    while (i >= 0 && messageList.get(i).isRemote) {
+      i--;
+    }
+    return i;
   }
 
   @Override