MessageAdapter.java

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