1package eu.siacs.conversations.ui.adapter;
2
3import android.content.Intent;
4import android.content.pm.PackageManager;
5import android.content.pm.ResolveInfo;
6import android.graphics.Typeface;
7import android.net.Uri;
8import android.text.Spannable;
9import android.text.SpannableString;
10import android.text.style.ForegroundColorSpan;
11import android.text.style.StyleSpan;
12import android.util.DisplayMetrics;
13import android.util.Log;
14import android.view.View;
15import android.view.View.OnClickListener;
16import android.view.View.OnLongClickListener;
17import android.view.ViewGroup;
18import android.widget.ArrayAdapter;
19import android.widget.Button;
20import android.widget.ImageView;
21import android.widget.LinearLayout;
22import android.widget.TextView;
23import android.widget.Toast;
24
25import java.util.List;
26
27import eu.siacs.conversations.Config;
28import eu.siacs.conversations.R;
29import eu.siacs.conversations.entities.Account;
30import eu.siacs.conversations.entities.Contact;
31import eu.siacs.conversations.entities.Conversation;
32import eu.siacs.conversations.entities.Downloadable;
33import eu.siacs.conversations.entities.DownloadableFile;
34import eu.siacs.conversations.entities.Message;
35import eu.siacs.conversations.entities.Message.ImageParams;
36import eu.siacs.conversations.ui.ConversationActivity;
37import eu.siacs.conversations.utils.UIHelper;
38import eu.siacs.conversations.xmpp.jid.Jid;
39
40public class MessageAdapter extends ArrayAdapter<Message> {
41
42 private static final int SENT = 0;
43 private static final int RECEIVED = 1;
44 private static final int STATUS = 2;
45 private static final int NULL = 3;
46
47 private ConversationActivity activity;
48
49 private DisplayMetrics metrics;
50
51 private OnContactPictureClicked mOnContactPictureClickedListener;
52 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
53
54 private OnLongClickListener openContextMenu = new OnLongClickListener() {
55
56 @Override
57 public boolean onLongClick(View v) {
58 v.showContextMenu();
59 return true;
60 }
61 };
62
63 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
64 super(activity, 0, messages);
65 this.activity = activity;
66 metrics = getContext().getResources().getDisplayMetrics();
67 }
68
69 public void setOnContactPictureClicked(OnContactPictureClicked listener) {
70 this.mOnContactPictureClickedListener = listener;
71 }
72
73 public void setOnContactPictureLongClicked(
74 OnContactPictureLongClicked listener) {
75 this.mOnContactPictureLongClickedListener = listener;
76 }
77
78 @Override
79 public int getViewTypeCount() {
80 return 4;
81 }
82
83 @Override
84 public int getItemViewType(int position) {
85 if (getItem(position).wasMergedIntoPrevious()) {
86 return NULL;
87 } else if (getItem(position).getType() == Message.TYPE_STATUS) {
88 return STATUS;
89 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
90 return RECEIVED;
91 } else {
92 return SENT;
93 }
94 }
95
96 private void displayStatus(ViewHolder viewHolder, Message message) {
97 String filesize = null;
98 String info = null;
99 boolean error = false;
100 if (viewHolder.indicatorReceived != null) {
101 viewHolder.indicatorReceived.setVisibility(View.GONE);
102 }
103 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
104 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
105 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
106 ImageParams params = message.getImageParams();
107 if (params.size > (1.5 * 1024 * 1024)) {
108 filesize = params.size / (1024 * 1024)+ " MiB";
109 } else if (params.size > 0) {
110 filesize = params.size / 1024 + " KiB";
111 }
112 if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
113 error = true;
114 }
115 }
116 switch (message.getMergedStatus()) {
117 case Message.STATUS_WAITING:
118 info = getContext().getString(R.string.waiting);
119 break;
120 case Message.STATUS_UNSEND:
121 Downloadable d = message.getDownloadable();
122 if (d!=null) {
123 info = getContext().getString(R.string.sending_file,d.getProgress());
124 } else {
125 info = getContext().getString(R.string.sending);
126 }
127 break;
128 case Message.STATUS_OFFERED:
129 info = getContext().getString(R.string.offering);
130 break;
131 case Message.STATUS_SEND_RECEIVED:
132 if (activity.indicateReceived()) {
133 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
134 }
135 break;
136 case Message.STATUS_SEND_DISPLAYED:
137 if (activity.indicateReceived()) {
138 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
139 }
140 break;
141 case Message.STATUS_SEND_FAILED:
142 info = getContext().getString(R.string.send_failed);
143 error = true;
144 break;
145 default:
146 if (multiReceived) {
147 info = getMessageDisplayName(message);
148 }
149 break;
150 }
151 if (error) {
152 viewHolder.time.setTextColor(activity.getWarningTextColor());
153 } else {
154 viewHolder.time.setTextColor(activity.getSecondaryTextColor());
155 }
156 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
157 viewHolder.indicator.setVisibility(View.GONE);
158 } else {
159 viewHolder.indicator.setVisibility(View.VISIBLE);
160 }
161
162 String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
163 message.getMergedTimeSent());
164 if (message.getStatus() <= Message.STATUS_RECEIVED) {
165 if ((filesize != null) && (info != null)) {
166 viewHolder.time.setText(filesize + " \u00B7 " + info);
167 } else if ((filesize == null) && (info != null)) {
168 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
169 } else if ((filesize != null) && (info == null)) {
170 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
171 } else {
172 viewHolder.time.setText(formatedTime);
173 }
174 } else {
175 if ((filesize != null) && (info != null)) {
176 viewHolder.time.setText(filesize + " \u00B7 " + info);
177 } else if ((filesize == null) && (info != null)) {
178 if (error) {
179 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
180 } else {
181 viewHolder.time.setText(info);
182 }
183 } else if ((filesize != null) && (info == null)) {
184 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
185 } else {
186 viewHolder.time.setText(formatedTime);
187 }
188 }
189 }
190
191 private void displayInfoMessage(ViewHolder viewHolder, String text) {
192 if (viewHolder.download_button != null) {
193 viewHolder.download_button.setVisibility(View.GONE);
194 }
195 viewHolder.image.setVisibility(View.GONE);
196 viewHolder.messageBody.setVisibility(View.VISIBLE);
197 viewHolder.messageBody.setText(text);
198 viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
199 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
200 viewHolder.messageBody.setTextIsSelectable(false);
201 }
202
203 private void displayDecryptionFailed(ViewHolder viewHolder) {
204 if (viewHolder.download_button != null) {
205 viewHolder.download_button.setVisibility(View.GONE);
206 }
207 viewHolder.image.setVisibility(View.GONE);
208 viewHolder.messageBody.setVisibility(View.VISIBLE);
209 viewHolder.messageBody.setText(getContext().getString(
210 R.string.decryption_failed));
211 viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
212 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
213 viewHolder.messageBody.setTextIsSelectable(false);
214 }
215
216 private String getMessageDisplayName(final Message message) {
217 if (message.getStatus() == Message.STATUS_RECEIVED) {
218 if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
219 return getDisplayedMucCounterpart(message.getCounterpart());
220 } else {
221 final Contact contact = message.getContact();
222 return contact != null ? contact.getDisplayName() : "";
223 }
224 } else {
225 if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
226 return getDisplayedMucCounterpart(message.getConversation().getJid());
227 } else {
228 final Jid jid = message.getConversation().getAccount().getJid();
229 return jid.hasLocalpart() ? jid.getLocalpart() : jid.toDomainJid().toString();
230 }
231 }
232 }
233
234 private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
235 if (viewHolder.download_button != null) {
236 viewHolder.download_button.setVisibility(View.GONE);
237 }
238 viewHolder.image.setVisibility(View.GONE);
239 viewHolder.messageBody.setVisibility(View.VISIBLE);
240 if (message.getBody() != null) {
241 final String nick = getMessageDisplayName(message);
242 final String formattedBody = message.getMergedBody().replaceAll("^/me ", nick + " ");
243 if (message.getType() != Message.TYPE_PRIVATE) {
244 if (message.hasMeCommand()) {
245 final Spannable span = new SpannableString(formattedBody);
246 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
247 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
248 viewHolder.messageBody.setText(span);
249 } else {
250 viewHolder.messageBody.setText(message.getMergedBody());
251 }
252 } else {
253 String privateMarker;
254 if (message.getStatus() <= Message.STATUS_RECEIVED) {
255 privateMarker = activity
256 .getString(R.string.private_message);
257 } else {
258 final String to;
259 if (message.getCounterpart() != null) {
260 to = message.getCounterpart().getResourcepart();
261 } else {
262 to = "";
263 }
264 privateMarker = activity.getString(R.string.private_message_to, to);
265 }
266 final Spannable span = new SpannableString(privateMarker + " "
267 + formattedBody);
268 span.setSpan(new ForegroundColorSpan(activity
269 .getSecondaryTextColor()), 0, privateMarker
270 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
271 span.setSpan(new StyleSpan(Typeface.BOLD), 0,
272 privateMarker.length(),
273 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
274 if (message.hasMeCommand()) {
275 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
276 privateMarker.length() + 1 + nick.length(),
277 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
278 }
279 viewHolder.messageBody.setText(span);
280 }
281 } else {
282 viewHolder.messageBody.setText("");
283 }
284 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
285 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
286 viewHolder.messageBody.setTextIsSelectable(true);
287 }
288
289 private void displayDownloadableMessage(ViewHolder viewHolder,
290 final Message message, String text) {
291 viewHolder.image.setVisibility(View.GONE);
292 viewHolder.messageBody.setVisibility(View.GONE);
293 viewHolder.download_button.setVisibility(View.VISIBLE);
294 viewHolder.download_button.setText(text);
295 viewHolder.download_button.setOnClickListener(new OnClickListener() {
296
297 @Override
298 public void onClick(View v) {
299 startDownloadable(message);
300 }
301 });
302 viewHolder.download_button.setOnLongClickListener(openContextMenu);
303 }
304
305 private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
306 final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
307 viewHolder.image.setVisibility(View.GONE);
308 viewHolder.messageBody.setVisibility(View.GONE);
309 viewHolder.download_button.setVisibility(View.VISIBLE);
310 viewHolder.download_button.setText(activity.getString(R.string.open_file, file.getMimeType()));
311 viewHolder.download_button.setOnClickListener(new OnClickListener() {
312
313 @Override
314 public void onClick(View v) {
315 openDownloadable(file);
316 }
317 });
318 viewHolder.download_button.setOnLongClickListener(openContextMenu);
319 }
320
321 private void displayImageMessage(ViewHolder viewHolder,
322 final Message message) {
323 if (viewHolder.download_button != null) {
324 viewHolder.download_button.setVisibility(View.GONE);
325 }
326 viewHolder.messageBody.setVisibility(View.GONE);
327 viewHolder.image.setVisibility(View.VISIBLE);
328 ImageParams params = message.getImageParams();
329 double target = metrics.density * 288;
330 int scalledW;
331 int scalledH;
332 if (params.width <= params.height) {
333 scalledW = (int) (params.width / ((double) params.height / target));
334 scalledH = (int) target;
335 } else {
336 scalledW = (int) target;
337 scalledH = (int) (params.height / ((double) params.width / target));
338 }
339 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
340 scalledW, scalledH));
341 activity.loadBitmap(message, viewHolder.image);
342 viewHolder.image.setOnClickListener(new OnClickListener() {
343
344 @Override
345 public void onClick(View v) {
346 Intent intent = new Intent(Intent.ACTION_VIEW);
347 intent.setDataAndType(activity.xmppConnectionService
348 .getFileBackend().getJingleFileUri(message), "image/*");
349 getContext().startActivity(intent);
350 }
351 });
352 viewHolder.image.setOnLongClickListener(openContextMenu);
353 }
354
355 private String getDisplayedMucCounterpart(final Jid counterpart) {
356 if (counterpart==null) {
357 return "";
358 } else if (!counterpart.isBareJid()) {
359 return counterpart.getResourcepart();
360 } else {
361 return counterpart.toString();
362 }
363 }
364
365 @Override
366 public View getView(int position, View view, ViewGroup parent) {
367 final Message message = getItem(position);
368 final Conversation conversation = message.getConversation();
369 final Account account = conversation.getAccount();
370 final int type = getItemViewType(position);
371 ViewHolder viewHolder;
372 if (view == null) {
373 viewHolder = new ViewHolder();
374 switch (type) {
375 case NULL:
376 view = activity.getLayoutInflater().inflate(
377 R.layout.message_null, parent, false);
378 break;
379 case SENT:
380 view = activity.getLayoutInflater().inflate(
381 R.layout.message_sent, parent, false);
382 viewHolder.message_box = (LinearLayout) view
383 .findViewById(R.id.message_box);
384 viewHolder.contact_picture = (ImageView) view
385 .findViewById(R.id.message_photo);
386 viewHolder.download_button = (Button) view
387 .findViewById(R.id.download_button);
388 viewHolder.indicator = (ImageView) view
389 .findViewById(R.id.security_indicator);
390 viewHolder.image = (ImageView) view
391 .findViewById(R.id.message_image);
392 viewHolder.messageBody = (TextView) view
393 .findViewById(R.id.message_body);
394 viewHolder.time = (TextView) view
395 .findViewById(R.id.message_time);
396 viewHolder.indicatorReceived = (ImageView) view
397 .findViewById(R.id.indicator_received);
398 break;
399 case RECEIVED:
400 view = activity.getLayoutInflater().inflate(
401 R.layout.message_received, parent, false);
402 viewHolder.message_box = (LinearLayout) view
403 .findViewById(R.id.message_box);
404 viewHolder.contact_picture = (ImageView) view
405 .findViewById(R.id.message_photo);
406 viewHolder.download_button = (Button) view
407 .findViewById(R.id.download_button);
408 viewHolder.indicator = (ImageView) view
409 .findViewById(R.id.security_indicator);
410 viewHolder.image = (ImageView) view
411 .findViewById(R.id.message_image);
412 viewHolder.messageBody = (TextView) view
413 .findViewById(R.id.message_body);
414 viewHolder.time = (TextView) view
415 .findViewById(R.id.message_time);
416 viewHolder.indicatorReceived = (ImageView) view
417 .findViewById(R.id.indicator_received);
418 break;
419 case STATUS:
420 view = activity.getLayoutInflater().inflate(
421 R.layout.message_status, parent, false);
422 viewHolder.contact_picture = (ImageView) view
423 .findViewById(R.id.message_photo);
424 break;
425 default:
426 viewHolder = null;
427 break;
428 }
429 view.setTag(viewHolder);
430 } else {
431 viewHolder = (ViewHolder) view.getTag();
432 if (viewHolder == null) {
433 return view;
434 }
435 }
436
437 if (type == STATUS) {
438 if (conversation.getMode() == Conversation.MODE_SINGLE) {
439 viewHolder.contact_picture.setImageBitmap(activity
440 .avatarService().get(conversation.getContact(),
441 activity.getPixel(32)));
442 viewHolder.contact_picture.setAlpha(0.5f);
443 viewHolder.contact_picture
444 .setOnClickListener(new OnClickListener() {
445
446 @Override
447 public void onClick(View v) {
448 String name = conversation.getName();
449 String read = getContext()
450 .getString(
451 R.string.contact_has_read_up_to_this_point,
452 name);
453 Toast.makeText(getContext(), read,
454 Toast.LENGTH_SHORT).show();
455 }
456 });
457
458 }
459 return view;
460 } else if (type == NULL) {
461 if (viewHolder.message_box != null) {
462 Log.e(Config.LOGTAG, "detected type=NULL but with wrong cached view");
463 view = activity.getLayoutInflater().inflate(R.layout.message_null, parent, false);
464 view.setTag(new ViewHolder());
465 }
466 if (position == getCount() - 1) {
467 view.getLayoutParams().height = 1;
468 } else {
469 view.getLayoutParams().height = 0;
470
471 }
472 view.setLayoutParams(view.getLayoutParams());
473 return view;
474 } else if (message.wasMergedIntoPrevious()) {
475 Log.e(Config.LOGTAG,"detected wasMergedIntoPrevious with wrong type");
476 return view;
477 } else if (viewHolder.messageBody == null || viewHolder.image == null) {
478 return view; //avoiding weird platform bugs
479 } else if (type == RECEIVED) {
480 Contact contact = message.getContact();
481 if (contact != null) {
482 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
483 } else if (conversation.getMode() == Conversation.MODE_MULTI) {
484 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()),
485 activity.getPixel(48)));
486 }
487 } else if (type == SENT) {
488 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
489 }
490
491 viewHolder.contact_picture
492 .setOnClickListener(new OnClickListener() {
493
494 @Override
495 public void onClick(View v) {
496 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
497 MessageAdapter.this.mOnContactPictureClickedListener
498 .onContactPictureClicked(message);
499 }
500
501 }
502 });
503 viewHolder.contact_picture
504 .setOnLongClickListener(new OnLongClickListener() {
505
506 @Override
507 public boolean onLongClick(View v) {
508 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
509 MessageAdapter.this.mOnContactPictureLongClickedListener
510 .onContactPictureLongClicked(message);
511 return true;
512 } else {
513 return false;
514 }
515 }
516 });
517
518 if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
519 Downloadable d = message.getDownloadable();
520 if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
521 if (message.getType() == Message.TYPE_FILE) {
522 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
523 } else {
524 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
525 }
526 } else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
527 displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
528 } else if (d.getStatus() == Downloadable.STATUS_DELETED) {
529 if (message.getType() == Message.TYPE_FILE) {
530 displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
531 } else {
532 displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
533 }
534 } else if (d.getStatus() == Downloadable.STATUS_OFFER) {
535 if (message.getType() == Message.TYPE_FILE) {
536 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType()));
537 } else {
538 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image));
539 }
540 } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
541 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize));
542 } else if (d.getStatus() == Downloadable.STATUS_FAILED) {
543 if (message.getType() == Message.TYPE_FILE) {
544 displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
545 } else {
546 displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
547 }
548 }
549 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
550 displayImageMessage(viewHolder, message);
551 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
552 if (message.getImageParams().width > 0) {
553 displayImageMessage(viewHolder,message);
554 } else {
555 displayOpenableMessage(viewHolder, message);
556 }
557 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
558 if (activity.hasPgp()) {
559 displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
560 } else {
561 displayInfoMessage(viewHolder,
562 activity.getString(R.string.install_openkeychain));
563 if (viewHolder != null) {
564 viewHolder.message_box
565 .setOnClickListener(new OnClickListener() {
566
567 @Override
568 public void onClick(View v) {
569 activity.showInstallPgpDialog();
570 }
571 });
572 }
573 }
574 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
575 displayDecryptionFailed(viewHolder);
576 } else {
577 displayTextMessage(viewHolder, message);
578 }
579
580 displayStatus(viewHolder, message);
581
582 return view;
583 }
584
585 public void startDownloadable(Message message) {
586 Downloadable downloadable = message.getDownloadable();
587 if (downloadable != null) {
588 if (!downloadable.start()) {
589 Toast.makeText(activity, R.string.not_connected_try_again,
590 Toast.LENGTH_SHORT).show();
591 }
592 }
593 }
594
595 public void openDownloadable(DownloadableFile file) {
596 if (!file.exists()) {
597 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
598 return;
599 }
600 Intent openIntent = new Intent(Intent.ACTION_VIEW);
601 openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
602 PackageManager manager = activity.getPackageManager();
603 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
604 if (infos.size() > 0) {
605 getContext().startActivity(openIntent);
606 } else {
607 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
608 }
609 }
610
611 public interface OnContactPictureClicked {
612 public void onContactPictureClicked(Message message);
613 }
614
615 public interface OnContactPictureLongClicked {
616 public void onContactPictureLongClicked(Message message);
617 }
618
619 private static class ViewHolder {
620
621 protected LinearLayout message_box;
622 protected Button download_button;
623 protected ImageView image;
624 protected ImageView indicator;
625 protected ImageView indicatorReceived;
626 protected TextView time;
627 protected TextView messageBody;
628 protected ImageView contact_picture;
629
630 }
631}