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