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.impl; 18 19 import android.content.Context; 20 import android.graphics.drawable.Drawable; 21 import android.support.annotation.IntDef; 22 import android.support.annotation.Nullable; 23 import android.support.v7.widget.RecyclerView; 24 import android.support.v7.widget.RecyclerView.ViewHolder; 25 import android.text.TextUtils; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import com.android.dialer.common.LogUtil; 30 import com.android.dialer.rtt.RttTranscript; 31 import com.android.dialer.rtt.RttTranscriptMessage; 32 import com.android.incallui.rtt.protocol.RttChatMessage; 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** Adapter class for holding RTT chat data. */ 39 public class RttChatAdapter extends RecyclerView.Adapter<ViewHolder> { 40 41 /** IntDef for the different types of rows that can be shown in the call log. */ 42 @Retention(RetentionPolicy.SOURCE) 43 @IntDef({ 44 RowType.ADVISORY, 45 RowType.MESSAGE, 46 }) 47 @interface RowType { 48 /** The transcript advisory message. */ 49 int ADVISORY = 1; 50 51 /** RTT chat message. */ 52 int MESSAGE = 2; 53 } 54 55 private static final int POSITION_ADVISORY = 0; 56 57 private Drawable avatarDrawable; 58 59 interface MessageListener { onUpdateRemoteMessage(int position)60 void onUpdateRemoteMessage(int position); 61 onUpdateLocalMessage(int position)62 void onUpdateLocalMessage(int position); 63 } 64 65 private final Context context; 66 private List<RttChatMessage> rttMessages = new ArrayList<>(); 67 private int lastIndexOfLocalMessage = -1; 68 private final MessageListener messageListener; 69 private boolean shouldShowAdvisory; 70 RttChatAdapter(Context context, MessageListener listener)71 RttChatAdapter(Context context, MessageListener listener) { 72 this.context = context; 73 this.messageListener = listener; 74 } 75 76 @Override onCreateViewHolder(ViewGroup parent, @RowType int viewType)77 public ViewHolder onCreateViewHolder(ViewGroup parent, @RowType int viewType) { 78 LayoutInflater layoutInflater = LayoutInflater.from(context); 79 switch (viewType) { 80 case RowType.ADVISORY: 81 View view = layoutInflater.inflate(R.layout.rtt_transcript_advisory, parent, false); 82 return new AdvisoryViewHolder(view); 83 case RowType.MESSAGE: 84 view = layoutInflater.inflate(R.layout.rtt_chat_list_item, parent, false); 85 return new RttChatMessageViewHolder(view); 86 default: 87 throw new RuntimeException("Unknown row type."); 88 } 89 } 90 91 @Override getItemViewType(int position)92 public int getItemViewType(int position) { 93 if (shouldShowAdvisory && position == POSITION_ADVISORY) { 94 return RowType.ADVISORY; 95 } else { 96 return RowType.MESSAGE; 97 } 98 } 99 100 @Override onBindViewHolder(ViewHolder viewHolder, int itemPosition)101 public void onBindViewHolder(ViewHolder viewHolder, int itemPosition) { 102 switch (getItemViewType(itemPosition)) { 103 case RowType.ADVISORY: 104 return; 105 case RowType.MESSAGE: 106 RttChatMessageViewHolder rttChatMessageViewHolder = (RttChatMessageViewHolder) viewHolder; 107 int messagePosition = toMessagePosition(itemPosition); 108 boolean isSameGroup = false; 109 if (messagePosition > 0) { 110 isSameGroup = 111 rttMessages.get(messagePosition).isRemote 112 == rttMessages.get(messagePosition - 1).isRemote; 113 } 114 rttChatMessageViewHolder.setMessage( 115 rttMessages.get(messagePosition), isSameGroup, avatarDrawable); 116 return; 117 default: 118 throw new RuntimeException("Unknown row type."); 119 } 120 } 121 122 @Override getItemCount()123 public int getItemCount() { 124 return shouldShowAdvisory ? rttMessages.size() + 1 : rttMessages.size(); 125 } 126 updateCurrentLocalMessage(String newMessage)127 private void updateCurrentLocalMessage(String newMessage) { 128 RttChatMessage rttChatMessage = null; 129 if (lastIndexOfLocalMessage >= 0) { 130 rttChatMessage = rttMessages.get(lastIndexOfLocalMessage); 131 } 132 if (rttChatMessage == null || rttChatMessage.isFinished()) { 133 rttChatMessage = new RttChatMessage(); 134 rttChatMessage.append(newMessage); 135 rttMessages.add(rttChatMessage); 136 lastIndexOfLocalMessage = rttMessages.size() - 1; 137 notifyItemInserted(toItemPosition(lastIndexOfLocalMessage)); 138 } else { 139 rttChatMessage.append(newMessage); 140 // Clear empty message bubble. 141 if (TextUtils.isEmpty(rttChatMessage.getContent())) { 142 rttMessages.remove(lastIndexOfLocalMessage); 143 notifyItemRemoved(toItemPosition(lastIndexOfLocalMessage)); 144 lastIndexOfLocalMessage = -1; 145 } else { 146 notifyItemChanged(toItemPosition(lastIndexOfLocalMessage)); 147 } 148 } 149 } 150 toMessagePosition(int itemPosition)151 private int toMessagePosition(int itemPosition) { 152 if (shouldShowAdvisory) { 153 return itemPosition - 1; 154 } else { 155 return itemPosition; 156 } 157 } 158 159 // Converts position in message list into item position in adapter. toItemPosition(int messagePosition)160 private int toItemPosition(int messagePosition) { 161 if (messagePosition < 0) { 162 return messagePosition; 163 } 164 if (shouldShowAdvisory) { 165 return messagePosition + 1; 166 } else { 167 return messagePosition; 168 } 169 } 170 updateCurrentRemoteMessage(String newMessage)171 private void updateCurrentRemoteMessage(String newMessage) { 172 RttChatMessage.updateRemoteRttChatMessage(rttMessages, newMessage); 173 lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages); 174 notifyDataSetChanged(); 175 } 176 addLocalMessage(String message)177 void addLocalMessage(String message) { 178 updateCurrentLocalMessage(message); 179 if (messageListener != null) { 180 messageListener.onUpdateLocalMessage(toItemPosition(lastIndexOfLocalMessage)); 181 } 182 } 183 submitLocalMessage()184 void submitLocalMessage() { 185 LogUtil.enterBlock("RttChatAdapater.submitLocalMessage"); 186 rttMessages.get(lastIndexOfLocalMessage).finish(); 187 notifyItemChanged(toItemPosition(lastIndexOfLocalMessage)); 188 lastIndexOfLocalMessage = -1; 189 } 190 computeChangeOfLocalMessage(String newMessage)191 String computeChangeOfLocalMessage(String newMessage) { 192 RttChatMessage rttChatMessage = null; 193 if (lastIndexOfLocalMessage >= 0) { 194 rttChatMessage = rttMessages.get(lastIndexOfLocalMessage); 195 } 196 if (rttChatMessage == null || rttChatMessage.isFinished()) { 197 return newMessage; 198 } else { 199 return RttChatMessage.computeChangedString(rttChatMessage.getContent(), newMessage); 200 } 201 } 202 addRemoteMessage(String message)203 void addRemoteMessage(String message) { 204 if (TextUtils.isEmpty(message)) { 205 return; 206 } 207 updateCurrentRemoteMessage(message); 208 if (messageListener != null) { 209 messageListener.onUpdateRemoteMessage( 210 toItemPosition(RttChatMessage.getLastIndexRemoteMessage(rttMessages))); 211 } 212 } 213 hideAdvisory()214 void hideAdvisory() { 215 shouldShowAdvisory = false; 216 notifyItemRemoved(POSITION_ADVISORY); 217 } 218 showAdvisory()219 void showAdvisory() { 220 shouldShowAdvisory = true; 221 notifyItemInserted(POSITION_ADVISORY); 222 } 223 224 /** 225 * Retrieve last local message and update the index. This is used when deleting to previous 226 * message bubble. 227 */ 228 @Nullable retrieveLastLocalMessage()229 String retrieveLastLocalMessage() { 230 lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages); 231 if (lastIndexOfLocalMessage >= 0) { 232 RttChatMessage rttChatMessage = rttMessages.get(lastIndexOfLocalMessage); 233 rttChatMessage.unfinish(); 234 return rttChatMessage.getContent(); 235 } else { 236 return null; 237 } 238 } 239 setAvatarDrawable(Drawable drawable)240 void setAvatarDrawable(Drawable drawable) { 241 avatarDrawable = drawable; 242 } 243 244 /** 245 * Restores RTT chat history from {@code RttTranscript}. 246 * 247 * @param rttTranscript transcript saved previously. 248 * @return last unfinished local message, return null if there is no current editing local 249 * message. 250 */ 251 @Nullable onRestoreRttChat(RttTranscript rttTranscript)252 String onRestoreRttChat(RttTranscript rttTranscript) { 253 LogUtil.enterBlock("RttChatAdapater.onRestoreRttChat"); 254 rttMessages = RttChatMessage.fromTranscript(rttTranscript); 255 lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages); 256 notifyDataSetChanged(); 257 if (lastIndexOfLocalMessage < 0) { 258 return null; 259 } 260 RttChatMessage message = rttMessages.get(lastIndexOfLocalMessage); 261 if (!message.isFinished()) { 262 return message.getContent(); 263 } else { 264 return null; 265 } 266 } 267 getRttTranscriptMessageList()268 List<RttTranscriptMessage> getRttTranscriptMessageList() { 269 return RttChatMessage.toTranscriptMessageList(rttMessages); 270 } 271 } 272