MessageAdapter.java

  1package eu.siacs.conversations.ui.adapter;
  2
  3import java.util.HashMap;
  4import java.util.List;
  5
  6import eu.siacs.conversations.Config;
  7import eu.siacs.conversations.R;
  8import eu.siacs.conversations.entities.Contact;
  9import eu.siacs.conversations.entities.Conversation;
 10import eu.siacs.conversations.entities.Downloadable;
 11import eu.siacs.conversations.entities.Message;
 12import eu.siacs.conversations.ui.ConversationActivity;
 13import eu.siacs.conversations.utils.UIHelper;
 14import android.content.Context;
 15import android.content.Intent;
 16import android.graphics.Bitmap;
 17import android.graphics.Typeface;
 18import android.text.Spannable;
 19import android.text.SpannableString;
 20import android.text.style.ForegroundColorSpan;
 21import android.text.style.StyleSpan;
 22import android.util.DisplayMetrics;
 23import android.view.View;
 24import android.view.ViewGroup;
 25import android.view.View.OnClickListener;
 26import android.view.View.OnLongClickListener;
 27import android.widget.ArrayAdapter;
 28import android.widget.Button;
 29import android.widget.ImageView;
 30import android.widget.LinearLayout;
 31import android.widget.TextView;
 32import android.widget.Toast;
 33
 34public class MessageAdapter extends ArrayAdapter<Message> {
 35
 36	private static final int SENT = 0;
 37	private static final int RECEIVED = 1;
 38	private static final int STATUS = 2;
 39	private static final int NULL = 3;
 40
 41	private ConversationActivity activity;
 42
 43	private Bitmap accountBitmap;
 44
 45	private BitmapCache mBitmapCache = new BitmapCache();
 46	private DisplayMetrics metrics;
 47
 48	private OnContactPictureClicked mOnContactPictureClickedListener;
 49	private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
 50
 51	public MessageAdapter(ConversationActivity activity, List<Message> messages) {
 52		super(activity, 0, messages);
 53		this.activity = activity;
 54		metrics = getContext().getResources().getDisplayMetrics();
 55	}
 56
 57	private Bitmap getSelfBitmap() {
 58		if (this.accountBitmap == null) {
 59
 60			if (getCount() > 0) {
 61				this.accountBitmap = getItem(0).getConversation().getAccount()
 62						.getImage(getContext(), 48);
 63			}
 64		}
 65		return this.accountBitmap;
 66	}
 67
 68	public void setOnContactPictureClicked(OnContactPictureClicked listener) {
 69		this.mOnContactPictureClickedListener = listener;
 70	}
 71
 72	public void setOnContactPictureLongClicked(
 73			OnContactPictureLongClicked listener) {
 74		this.mOnContactPictureLongClickedListener = listener;
 75	}
 76
 77	@Override
 78	public int getViewTypeCount() {
 79		return 4;
 80	}
 81
 82	@Override
 83	public int getItemViewType(int position) {
 84		if (getItem(position).wasMergedIntoPrevious()) {
 85			return NULL;
 86		} else 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) {
105			String[] fileParams = message.getBody().split(",");
106			try {
107				long size = Long.parseLong(fileParams[0]);
108				filesize = size / 1024 + " KB";
109			} catch (NumberFormatException e) {
110				filesize = "0 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				String body = Config.PARSE_EMOTICONS ? UIHelper
234						.transformAsciiEmoticons(message.getMergedBody())
235						: message.getMergedBody();
236				viewHolder.messageBody.setText(body);
237			} else {
238				String privateMarker;
239				if (message.getStatus() <= Message.STATUS_RECEIVED) {
240					privateMarker = activity
241							.getString(R.string.private_message);
242				} else {
243					String to;
244					if (message.getPresence() != null) {
245						to = message.getPresence();
246					} else {
247						to = message.getCounterpart();
248					}
249					privateMarker = activity.getString(
250							R.string.private_message_to, to);
251				}
252				SpannableString span = new SpannableString(privateMarker + " "
253						+ message.getBody());
254				span.setSpan(
255						new ForegroundColorSpan(activity
256								.getSecondaryTextColor()), 0, privateMarker
257								.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
258				span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
259						privateMarker.length(),
260						Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
261				viewHolder.messageBody.setText(span);
262			}
263		} else {
264			viewHolder.messageBody.setText("");
265		}
266		viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
267		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
268		viewHolder.messageBody.setTextIsSelectable(true);
269	}
270
271	private void displayImageMessage(ViewHolder viewHolder,
272			final Message message) {
273		if (viewHolder.download_button != null) {
274			viewHolder.download_button.setVisibility(View.GONE);
275		}
276		viewHolder.messageBody.setVisibility(View.GONE);
277		viewHolder.image.setVisibility(View.VISIBLE);
278		String[] fileParams = message.getBody().split(",");
279		if (fileParams.length == 3) {
280			double target = metrics.density * 288;
281			int w = Integer.parseInt(fileParams[1]);
282			int h = Integer.parseInt(fileParams[2]);
283			int scalledW;
284			int scalledH;
285			if (w <= h) {
286				scalledW = (int) (w / ((double) h / target));
287				scalledH = (int) target;
288			} else {
289				scalledW = (int) target;
290				scalledH = (int) (h / ((double) w / target));
291			}
292			viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
293					scalledW, scalledH));
294		}
295		activity.loadBitmap(message, viewHolder.image);
296		viewHolder.image.setOnClickListener(new OnClickListener() {
297
298			@Override
299			public void onClick(View v) {
300				Intent intent = new Intent(Intent.ACTION_VIEW);
301				intent.setDataAndType(activity.xmppConnectionService
302						.getFileBackend().getJingleFileUri(message), "image/*");
303				getContext().startActivity(intent);
304			}
305		});
306		viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
307
308			@Override
309			public boolean onLongClick(View v) {
310				Intent shareIntent = new Intent();
311				shareIntent.setAction(Intent.ACTION_SEND);
312				shareIntent.putExtra(Intent.EXTRA_STREAM,
313						activity.xmppConnectionService.getFileBackend()
314								.getJingleFileUri(message));
315				shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
316				shareIntent.setType("image/webp");
317				getContext().startActivity(
318						Intent.createChooser(shareIntent,
319								getContext().getText(R.string.share_with)));
320				return true;
321			}
322		});
323	}
324
325	@Override
326	public View getView(int position, View view, ViewGroup parent) {
327		final Message item = getItem(position);
328		int type = getItemViewType(position);
329		ViewHolder viewHolder;
330		if (view == null) {
331			viewHolder = new ViewHolder();
332			switch (type) {
333			case NULL:
334				view = (View) activity.getLayoutInflater().inflate(
335						R.layout.message_null, parent, false);
336				break;
337			case SENT:
338				view = (View) activity.getLayoutInflater().inflate(
339						R.layout.message_sent, parent, false);
340				viewHolder.message_box = (LinearLayout) view
341						.findViewById(R.id.message_box);
342				viewHolder.contact_picture = (ImageView) view
343						.findViewById(R.id.message_photo);
344				viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
345				viewHolder.indicator = (ImageView) view
346						.findViewById(R.id.security_indicator);
347				viewHolder.image = (ImageView) view
348						.findViewById(R.id.message_image);
349				viewHolder.messageBody = (TextView) view
350						.findViewById(R.id.message_body);
351				viewHolder.time = (TextView) view
352						.findViewById(R.id.message_time);
353				viewHolder.indicatorReceived = (ImageView) view
354						.findViewById(R.id.indicator_received);
355				view.setTag(viewHolder);
356				break;
357			case RECEIVED:
358				view = (View) activity.getLayoutInflater().inflate(
359						R.layout.message_received, parent, false);
360				viewHolder.message_box = (LinearLayout) view
361						.findViewById(R.id.message_box);
362				viewHolder.contact_picture = (ImageView) view
363						.findViewById(R.id.message_photo);
364
365				viewHolder.download_button = (Button) view
366						.findViewById(R.id.download_button);
367
368				if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
369
370					viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
371							item.getConversation().getContact(), getContext()));
372
373				}
374				viewHolder.indicator = (ImageView) view
375						.findViewById(R.id.security_indicator);
376				viewHolder.image = (ImageView) view
377						.findViewById(R.id.message_image);
378				viewHolder.messageBody = (TextView) view
379						.findViewById(R.id.message_body);
380				viewHolder.time = (TextView) view
381						.findViewById(R.id.message_time);
382				view.setTag(viewHolder);
383				break;
384			case STATUS:
385				view = (View) activity.getLayoutInflater().inflate(
386						R.layout.message_status, parent, false);
387				viewHolder.contact_picture = (ImageView) view
388						.findViewById(R.id.message_photo);
389				if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
390
391					viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
392							item.getConversation().getContact(), getContext()));
393					viewHolder.contact_picture.setAlpha(128);
394					viewHolder.contact_picture
395							.setOnClickListener(new OnClickListener() {
396
397								@Override
398								public void onClick(View v) {
399									String name = item.getConversation()
400											.getName();
401									String read = getContext()
402											.getString(
403													R.string.contact_has_read_up_to_this_point,
404													name);
405									Toast.makeText(getContext(), read,
406											Toast.LENGTH_SHORT).show();
407								}
408							});
409
410				}
411				break;
412			default:
413				viewHolder = null;
414				break;
415			}
416		} else {
417			viewHolder = (ViewHolder) view.getTag();
418		}
419
420		if (type == STATUS) {
421			return view;
422		}
423		if (type == NULL) {
424			if (position == getCount() - 1) {
425				view.getLayoutParams().height = 1;
426			} else {
427				view.getLayoutParams().height = 0;
428				
429			}
430			view.setLayoutParams(view.getLayoutParams());
431			return view;
432		}
433
434		if (viewHolder.contact_picture != null) {
435			viewHolder.contact_picture
436					.setOnClickListener(new OnClickListener() {
437
438						@Override
439						public void onClick(View v) {
440							if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
441								MessageAdapter.this.mOnContactPictureClickedListener
442										.onContactPictureClicked(item);
443								;
444							}
445
446						}
447					});
448			viewHolder.contact_picture
449					.setOnLongClickListener(new OnLongClickListener() {
450
451						@Override
452						public boolean onLongClick(View v) {
453							if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
454								MessageAdapter.this.mOnContactPictureLongClickedListener
455										.onContactPictureLongClicked(item);
456								return true;
457							} else {
458								return false;
459							}
460						}
461					});
462		}
463
464		if (type == RECEIVED) {
465			if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
466				Contact contact = item.getContact();
467				if (contact != null) {
468					viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
469							contact, getContext()));
470				} else {
471					String name = item.getPresence();
472					if (name == null) {
473						name = item.getCounterpart();
474					}
475					viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
476							name, getContext()));
477				}
478			}
479		}
480
481		if (item.getType() == Message.TYPE_IMAGE) {
482			if (item.getStatus() == Message.STATUS_RECEIVING) {
483				displayInfoMessage(viewHolder, R.string.receiving_image);
484			} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
485				viewHolder.image.setVisibility(View.GONE);
486				viewHolder.messageBody.setVisibility(View.GONE);
487				viewHolder.download_button.setVisibility(View.VISIBLE);
488				viewHolder.download_button
489						.setOnClickListener(new OnClickListener() {
490
491							@Override
492							public void onClick(View v) {
493								Downloadable downloadable = item
494										.getDownloadable();
495								if (downloadable != null) {
496									downloadable.start();
497								}
498							}
499						});
500			} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
501					|| (item.getEncryption() == Message.ENCRYPTION_NONE)
502					|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
503				displayImageMessage(viewHolder, item);
504			} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
505				displayInfoMessage(viewHolder, R.string.encrypted_message);
506			} else {
507				displayDecryptionFailed(viewHolder);
508			}
509		} else {
510			if (item.getEncryption() == Message.ENCRYPTION_PGP) {
511				if (activity.hasPgp()) {
512					displayInfoMessage(viewHolder, R.string.encrypted_message);
513				} else {
514					displayInfoMessage(viewHolder,
515							R.string.install_openkeychain);
516					viewHolder.message_box
517							.setOnClickListener(new OnClickListener() {
518
519								@Override
520								public void onClick(View v) {
521									activity.showInstallPgpDialog();
522								}
523							});
524				}
525			} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
526				displayDecryptionFailed(viewHolder);
527			} else {
528				displayTextMessage(viewHolder, item);
529			}
530		}
531
532		displayStatus(viewHolder, item);
533
534		return view;
535	}
536
537	private static class ViewHolder {
538
539		protected LinearLayout message_box;
540		protected Button download_button;
541		protected ImageView image;
542		protected ImageView indicator;
543		protected ImageView indicatorReceived;
544		protected TextView time;
545		protected TextView messageBody;
546		protected ImageView contact_picture;
547
548	}
549
550	private class BitmapCache {
551		private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
552		private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
553
554		public Bitmap get(Contact contact, Context context) {
555			if (!contactBitmaps.containsKey(contact.getJid())) {
556				contactBitmaps.put(contact.getJid(),
557						contact.getImage(48, context));
558			}
559			return contactBitmaps.get(contact.getJid());
560		}
561
562		public Bitmap get(String name, Context context) {
563			if (unknownBitmaps.containsKey(name)) {
564				return unknownBitmaps.get(name);
565			} else {
566				Bitmap bm = UIHelper
567						.getContactPicture(name, 48, context, false);
568				unknownBitmaps.put(name, bm);
569				return bm;
570			}
571		}
572	}
573
574	public interface OnContactPictureClicked {
575		public void onContactPictureClicked(Message message);
576	}
577
578	public interface OnContactPictureLongClicked {
579		public void onContactPictureLongClicked(Message message);
580	}
581}