MessageAdapter.java

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