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