1 /*
2  * Copyright (C) 2018 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.incallui.rtt.protocol;
18 
19 import android.support.annotation.NonNull;
20 import com.android.dialer.common.Assert;
21 import com.android.dialer.rtt.RttTranscript;
22 import com.android.dialer.rtt.RttTranscriptMessage;
23 import com.google.common.base.Splitter;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.List;
27 
28 /** Message class that holds one RTT chat content. */
29 public final class RttChatMessage {
30 
31   private static final Splitter SPLITTER = Splitter.on(Constants.BUBBLE_BREAKER);
32 
33   public boolean isRemote;
34   private long timstamp;
35   private final StringBuilder content = new StringBuilder();
36   private boolean isFinished;
37 
isFinished()38   public boolean isFinished() {
39     return isFinished;
40   }
41 
finish()42   public void finish() {
43     isFinished = true;
44   }
45 
unfinish()46   public void unfinish() {
47     isFinished = false;
48   }
49 
append(String text)50   public void append(String text) {
51     for (int i = 0; i < text.length(); i++) {
52       char c = text.charAt(i);
53       if (c == '\b' && content.length() > 0 && content.charAt(content.length() - 1) != '\b') {
54         content.deleteCharAt(content.length() - 1);
55       } else {
56         content.append(c);
57       }
58     }
59   }
60 
getContent()61   public String getContent() {
62     return content.toString();
63   }
64 
65   /**
66    * Computes delta change of two string.
67    *
68    * <p>e.g. "hello world" -> "hello" : "\b\b\b\b\b\b"
69    *
70    * <p>"hello world" -> "hello mom!" : "\b\b\b\b\bmom!"
71    *
72    * <p>"hello world" -> "hello d" : "\b\b\b\b\bd"
73    *
74    * <p>"hello world" -> "hello new world" : "\b\b\b\b\bnew world"
75    */
computeChangedString(String oldMessage, String newMesssage)76   public static String computeChangedString(String oldMessage, String newMesssage) {
77     StringBuilder modify = new StringBuilder();
78     int indexChangeStart = 0;
79     while (indexChangeStart < oldMessage.length()
80         && indexChangeStart < newMesssage.length()
81         && oldMessage.charAt(indexChangeStart) == newMesssage.charAt(indexChangeStart)) {
82       indexChangeStart++;
83     }
84     for (int i = indexChangeStart; i < oldMessage.length(); i++) {
85       modify.append('\b');
86     }
87     for (int i = indexChangeStart; i < newMesssage.length(); i++) {
88       modify.append(newMesssage.charAt(i));
89     }
90     return modify.toString();
91   }
92 
getRttTranscriptWithNewRemoteMessage( RttTranscript rttTranscript, @NonNull String text)93   public static RttTranscript getRttTranscriptWithNewRemoteMessage(
94       RttTranscript rttTranscript, @NonNull String text) {
95     List<RttChatMessage> messageList = fromTranscript(rttTranscript);
96     updateRemoteRttChatMessage(messageList, text);
97     return RttTranscript.newBuilder()
98         .setId(rttTranscript.getId())
99         .setNumber(rttTranscript.getNumber())
100         .setTimestamp(rttTranscript.getTimestamp())
101         .addAllMessages(toTranscriptMessageList(messageList))
102         .build();
103   }
104 
105   /** Update list of {@code RttChatMessage} based on given remote text. */
updateRemoteRttChatMessage( List<RttChatMessage> messageList, @NonNull String text)106   public static void updateRemoteRttChatMessage(
107       List<RttChatMessage> messageList, @NonNull String text) {
108     Assert.isNotNull(messageList);
109     Iterator<String> splitText = SPLITTER.split(text).iterator();
110 
111     while (splitText.hasNext()) {
112       String singleMessageContent = splitText.next();
113       RttChatMessage message;
114       int index = getLastIndexUnfinishedRemoteMessage(messageList);
115       if (index < 0) {
116         message = new RttChatMessage();
117         message.append(singleMessageContent);
118         message.isRemote = true;
119         if (splitText.hasNext()) {
120           message.finish();
121         }
122         if (message.content.length() != 0) {
123           messageList.add(message);
124         }
125       } else {
126         message = messageList.get(index);
127         message.append(singleMessageContent);
128         if (splitText.hasNext()) {
129           message.finish();
130         }
131         if (message.content.length() == 0) {
132           messageList.remove(index);
133         }
134       }
135       StringBuilder content = message.content;
136       // Delete previous messages.
137       while (content.length() > 0 && content.charAt(0) == '\b') {
138         messageList.remove(message);
139         content.delete(0, 1);
140         int previous = getLastIndexRemoteMessage(messageList);
141         // There are more backspaces than existing characters.
142         if (previous < 0) {
143           while (content.length() > 0 && content.charAt(0) == '\b') {
144             content.deleteCharAt(0);
145           }
146           // Add message if there are still characters after backspaces.
147           if (content.length() > 0) {
148             message = new RttChatMessage();
149             message.append(content.toString());
150             message.isRemote = true;
151             if (splitText.hasNext()) {
152               message.finish();
153             }
154             messageList.add(message);
155           }
156           break;
157         }
158         message = messageList.get(previous);
159         message.unfinish();
160         message.append(content.toString());
161         content = message.content;
162       }
163     }
164     if (text.endsWith(Constants.BUBBLE_BREAKER)) {
165       int lastIndexRemoteMessage = getLastIndexRemoteMessage(messageList);
166       messageList.get(lastIndexRemoteMessage).finish();
167     }
168   }
169 
getLastIndexUnfinishedRemoteMessage(List<RttChatMessage> messageList)170   private static int getLastIndexUnfinishedRemoteMessage(List<RttChatMessage> messageList) {
171     int i = messageList.size() - 1;
172     while (i >= 0 && (!messageList.get(i).isRemote || messageList.get(i).isFinished)) {
173       i--;
174     }
175     return i;
176   }
177 
getLastIndexRemoteMessage(List<RttChatMessage> messageList)178   public static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) {
179     int i = messageList.size() - 1;
180     while (i >= 0 && !messageList.get(i).isRemote) {
181       i--;
182     }
183     return i;
184   }
185 
getLastIndexLocalMessage(List<RttChatMessage> messageList)186   public static int getLastIndexLocalMessage(List<RttChatMessage> messageList) {
187     int i = messageList.size() - 1;
188     while (i >= 0 && messageList.get(i).isRemote) {
189       i--;
190     }
191     return i;
192   }
193 
toTranscriptMessageList( List<RttChatMessage> messageList)194   public static List<RttTranscriptMessage> toTranscriptMessageList(
195       List<RttChatMessage> messageList) {
196     List<RttTranscriptMessage> transcriptMessageList = new ArrayList<>();
197     for (RttChatMessage message : messageList) {
198       transcriptMessageList.add(
199           RttTranscriptMessage.newBuilder()
200               .setContent(message.getContent())
201               .setTimestamp(message.timstamp)
202               .setIsRemote(message.isRemote)
203               .setIsFinished(message.isFinished)
204               .build());
205     }
206     return transcriptMessageList;
207   }
208 
fromTranscript(RttTranscript rttTranscript)209   public static List<RttChatMessage> fromTranscript(RttTranscript rttTranscript) {
210     List<RttChatMessage> messageList = new ArrayList<>();
211     if (rttTranscript == null) {
212       return messageList;
213     }
214     for (RttTranscriptMessage message : rttTranscript.getMessagesList()) {
215       RttChatMessage chatMessage = new RttChatMessage();
216       chatMessage.append(message.getContent());
217       chatMessage.timstamp = message.getTimestamp();
218       chatMessage.isRemote = message.getIsRemote();
219       if (message.getIsFinished()) {
220         chatMessage.finish();
221       }
222       messageList.add(chatMessage);
223     }
224     return messageList;
225   }
226 
RttChatMessage()227   public RttChatMessage() {
228     timstamp = System.currentTimeMillis();
229   }
230 }
231