Message.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5
  6import java.net.MalformedURLException;
  7import java.net.URL;
  8import java.util.Arrays;
  9
 10import eu.siacs.conversations.Config;
 11import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 12import eu.siacs.conversations.xmpp.jid.Jid;
 13
 14public class Message extends AbstractEntity {
 15
 16	public static final String TABLENAME = "messages";
 17
 18	public static final int STATUS_RECEIVED = 0;
 19	public static final int STATUS_UNSEND = 1;
 20	public static final int STATUS_SEND = 2;
 21	public static final int STATUS_SEND_FAILED = 3;
 22	public static final int STATUS_WAITING = 5;
 23	public static final int STATUS_OFFERED = 6;
 24	public static final int STATUS_SEND_RECEIVED = 7;
 25	public static final int STATUS_SEND_DISPLAYED = 8;
 26
 27	public static final int ENCRYPTION_NONE = 0;
 28	public static final int ENCRYPTION_PGP = 1;
 29	public static final int ENCRYPTION_OTR = 2;
 30	public static final int ENCRYPTION_DECRYPTED = 3;
 31	public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
 32
 33	public static final int TYPE_TEXT = 0;
 34	public static final int TYPE_IMAGE = 1;
 35	public static final int TYPE_FILE = 2;
 36	public static final int TYPE_STATUS = 3;
 37	public static final int TYPE_PRIVATE = 4;
 38
 39	public static String CONVERSATION = "conversationUuid";
 40	public static String COUNTERPART = "counterpart";
 41	public static String TRUE_COUNTERPART = "trueCounterpart";
 42	public static String BODY = "body";
 43	public static String TIME_SENT = "timeSent";
 44	public static String ENCRYPTION = "encryption";
 45	public static String STATUS = "status";
 46	public static String TYPE = "type";
 47	public static String REMOTE_MSG_ID = "remoteMsgId";
 48	public static String RELATIVE_FILE_PATH = "relativeFilePath";
 49	public boolean markable = false;
 50	protected String conversationUuid;
 51	protected Jid counterpart;
 52	protected Jid trueCounterpart;
 53	protected String body;
 54	protected String encryptedBody;
 55	protected long timeSent;
 56	protected int encryption;
 57	protected int status;
 58	protected int type;
 59	protected String relativeFilePath;
 60	protected boolean read = true;
 61	protected String remoteMsgId = null;
 62	protected Conversation conversation = null;
 63	protected Downloadable downloadable = null;
 64	private Message mNextMessage = null;
 65	private Message mPreviousMessage = null;
 66
 67	private Message() {
 68
 69	}
 70
 71	public Message(Conversation conversation, String body, int encryption) {
 72		this(conversation,body,encryption,STATUS_UNSEND);
 73	}
 74
 75	public Message(Conversation conversation, String body, int encryption, int status) {
 76		this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
 77				conversation.getContactJid().toBareJid(), null, body, System
 78						.currentTimeMillis(), encryption,
 79				status, TYPE_TEXT, null,null);
 80		this.conversation = conversation;
 81	}
 82
 83	public Message(final String uuid, final String conversationUUid, final Jid counterpart,
 84				   final Jid trueCounterpart, final String body, final long timeSent,
 85				   final int encryption, final int status, final int type, final String remoteMsgId, final String relativeFilePath) {
 86		this.uuid = uuid;
 87		this.conversationUuid = conversationUUid;
 88		this.counterpart = counterpart;
 89		this.trueCounterpart = trueCounterpart;
 90		this.body = body;
 91		this.timeSent = timeSent;
 92		this.encryption = encryption;
 93		this.status = status;
 94		this.type = type;
 95		this.remoteMsgId = remoteMsgId;
 96		this.relativeFilePath = relativeFilePath;
 97	}
 98
 99	public static Message fromCursor(Cursor cursor) {
100		Jid jid;
101		try {
102			String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
103			if (value!=null) {
104				jid = Jid.fromString(value);
105			} else {
106				jid = null;
107			}
108		} catch (InvalidJidException e) {
109			jid = null;
110		}
111		Jid trueCounterpart;
112		try {
113			String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
114			if (value!=null) {
115				trueCounterpart = Jid.fromString(value);
116			} else {
117				trueCounterpart = null;
118			}
119		} catch (InvalidJidException e) {
120			trueCounterpart = null;
121		}
122		return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
123				cursor.getString(cursor.getColumnIndex(CONVERSATION)),
124				jid,
125				trueCounterpart,
126				cursor.getString(cursor.getColumnIndex(BODY)),
127				cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
128				cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
129				cursor.getInt(cursor.getColumnIndex(STATUS)),
130				cursor.getInt(cursor.getColumnIndex(TYPE)),
131				cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
132				cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)));
133	}
134
135	public static Message createStatusMessage(Conversation conversation) {
136		Message message = new Message();
137		message.setType(Message.TYPE_STATUS);
138		message.setConversation(conversation);
139		return message;
140	}
141
142	@Override
143	public ContentValues getContentValues() {
144		ContentValues values = new ContentValues();
145		values.put(UUID, uuid);
146		values.put(CONVERSATION, conversationUuid);
147		if (counterpart == null) {
148			values.putNull(COUNTERPART);
149		} else {
150			values.put(COUNTERPART, counterpart.toString());
151		}
152		if (trueCounterpart == null ) {
153			values.putNull(TRUE_COUNTERPART);
154		} else {
155			values.put(TRUE_COUNTERPART, trueCounterpart.toString());
156		}
157		values.put(BODY, body);
158		values.put(TIME_SENT, timeSent);
159		values.put(ENCRYPTION, encryption);
160		values.put(STATUS, status);
161		values.put(TYPE, type);
162		values.put(REMOTE_MSG_ID, remoteMsgId);
163		values.put(RELATIVE_FILE_PATH, relativeFilePath);
164		return values;
165	}
166
167	public String getConversationUuid() {
168		return conversationUuid;
169	}
170
171	public Conversation getConversation() {
172		return this.conversation;
173	}
174
175	public void setConversation(Conversation conv) {
176		this.conversation = conv;
177	}
178
179	public Jid getCounterpart() {
180		return counterpart;
181	}
182
183	public void setCounterpart(final Jid counterpart) {
184		this.counterpart = counterpart;
185	}
186
187	public Contact getContact() {
188		if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
189			return this.conversation.getContact();
190		} else {
191			if (this.trueCounterpart == null) {
192				return null;
193			} else {
194				return this.conversation.getAccount().getRoster()
195						.getContactFromRoster(this.trueCounterpart);
196			}
197		}
198	}
199
200	public String getBody() {
201		return body;
202	}
203
204	public void setBody(String body) {
205		this.body = body;
206	}
207
208	public long getTimeSent() {
209		return timeSent;
210	}
211
212	public int getEncryption() {
213		return encryption;
214	}
215
216	public void setEncryption(int encryption) {
217		this.encryption = encryption;
218	}
219
220	public int getStatus() {
221		return status;
222	}
223
224	public void setStatus(int status) {
225		this.status = status;
226	}
227
228	public void setRelativeFilePath(String path) {
229		this.relativeFilePath = path;
230	}
231
232	public String getRelativeFilePath() {
233		return  this.relativeFilePath;
234	}
235
236	public String getRemoteMsgId() {
237		return this.remoteMsgId;
238	}
239
240	public void setRemoteMsgId(String id) {
241		this.remoteMsgId = id;
242	}
243
244	public boolean isRead() {
245		return this.read;
246	}
247
248	public void markRead() {
249		this.read = true;
250	}
251
252	public void markUnread() {
253		this.read = false;
254	}
255
256	public void setTime(long time) {
257		this.timeSent = time;
258	}
259
260	public String getEncryptedBody() {
261		return this.encryptedBody;
262	}
263
264	public void setEncryptedBody(String body) {
265		this.encryptedBody = body;
266	}
267
268	public int getType() {
269		return this.type;
270	}
271
272	public void setType(int type) {
273		this.type = type;
274	}
275
276	public void setTrueCounterpart(Jid trueCounterpart) {
277		this.trueCounterpart = trueCounterpart;
278	}
279
280	public Downloadable getDownloadable() {
281		return this.downloadable;
282	}
283
284	public void setDownloadable(Downloadable downloadable) {
285		this.downloadable = downloadable;
286	}
287
288	public boolean equals(Message message) {
289		if ((this.remoteMsgId != null) && (this.body != null)
290				&& (this.counterpart != null)) {
291			return this.remoteMsgId.equals(message.getRemoteMsgId())
292					&& this.body.equals(message.getBody())
293					&& this.counterpart.equals(message.getCounterpart());
294		} else {
295			return false;
296		}
297	}
298
299	public Message next() {
300		if (this.mNextMessage == null) {
301			synchronized (this.conversation.messages) {
302				int index = this.conversation.messages.indexOf(this);
303				if (index < 0
304						|| index >= this.conversation.getMessages().size() - 1) {
305					this.mNextMessage = null;
306				} else {
307					this.mNextMessage = this.conversation.messages
308							.get(index + 1);
309				}
310			}
311		}
312		return this.mNextMessage;
313	}
314
315	public Message prev() {
316		if (this.mPreviousMessage == null) {
317			synchronized (this.conversation.messages) {
318				int index = this.conversation.messages.indexOf(this);
319				if (index <= 0 || index > this.conversation.messages.size()) {
320					this.mPreviousMessage = null;
321				} else {
322					this.mPreviousMessage = this.conversation.messages
323							.get(index - 1);
324				}
325			}
326		}
327		return this.mPreviousMessage;
328	}
329
330	public boolean mergeable(Message message) {
331		if (message == null) {
332			return false;
333		}
334		return (message.getType() == Message.TYPE_TEXT
335				&& this.getDownloadable() == null
336				&& message.getDownloadable() == null
337				&& message.getEncryption() != Message.ENCRYPTION_PGP
338				&& this.getType() == message.getType()
339				&& this.getEncryption() == message.getEncryption()
340				&& this.getCounterpart() != null
341				&& this.getCounterpart().equals(message.getCounterpart())
342				&& (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
343				.getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
344				.getStatus() == Message.STATUS_SEND_RECEIVED) && (message
345				.getStatus() == Message.STATUS_UNSEND
346				|| message.getStatus() == Message.STATUS_SEND || message
347				.getStatus() == Message.STATUS_SEND_DISPLAYED))))
348				&& !message.bodyContainsDownloadable()
349				&& !this.bodyContainsDownloadable());
350	}
351
352	public String getMergedBody() {
353		Message next = this.next();
354		if (this.mergeable(next)) {
355			return body.trim() + '\n' + next.getMergedBody();
356		}
357		return body.trim();
358	}
359
360	public int getMergedStatus() {
361		Message next = this.next();
362		if (this.mergeable(next)) {
363			return next.getMergedStatus();
364		} else {
365			return getStatus();
366		}
367	}
368
369	public long getMergedTimeSent() {
370		Message next = this.next();
371		if (this.mergeable(next)) {
372			return next.getMergedTimeSent();
373		} else {
374			return getTimeSent();
375		}
376	}
377
378	public boolean wasMergedIntoPrevious() {
379		Message prev = this.prev();
380		return prev != null && prev.mergeable(this);
381	}
382
383	public boolean trusted() {
384		Contact contact = this.getContact();
385		return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
386	}
387
388	public boolean bodyContainsDownloadable() {
389		try {
390			URL url = new URL(this.getBody());
391			if (!url.getProtocol().equalsIgnoreCase("http")
392					&& !url.getProtocol().equalsIgnoreCase("https")) {
393				return false;
394			}
395			if (url.getPath() == null) {
396				return false;
397			}
398			String[] pathParts = url.getPath().split("/");
399			String filename;
400			if (pathParts.length > 0) {
401				filename = pathParts[pathParts.length - 1];
402			} else {
403				return false;
404			}
405			String[] extensionParts = filename.split("\\.");
406			if (extensionParts.length == 2
407					&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
408					extensionParts[extensionParts.length - 1])) {
409				return true;
410			} else if (extensionParts.length == 3
411					&& Arrays
412					.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
413					.contains(extensionParts[extensionParts.length - 1])
414					&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
415					extensionParts[extensionParts.length - 2])) {
416				return true;
417			} else {
418				return false;
419			}
420		} catch (MalformedURLException e) {
421			return false;
422		}
423	}
424
425	public ImageParams getImageParams() {
426		ImageParams params = getLegacyImageParams();
427		if (params != null) {
428			return params;
429		}
430		params = new ImageParams();
431		if (this.downloadable != null) {
432			params.size = this.downloadable.getFileSize();
433		}
434		if (body == null) {
435			return params;
436		}
437		String parts[] = body.split("\\|");
438		if (parts.length == 1) {
439			try {
440				params.size = Long.parseLong(parts[0]);
441			} catch (NumberFormatException e) {
442				params.origin = parts[0];
443				try {
444					params.url = new URL(parts[0]);
445				} catch (MalformedURLException e1) {
446					params.url = null;
447				}
448			}
449		} else if (parts.length == 3) {
450			try {
451				params.size = Long.parseLong(parts[0]);
452			} catch (NumberFormatException e) {
453				params.size = 0;
454			}
455			try {
456				params.width = Integer.parseInt(parts[1]);
457			} catch (NumberFormatException e) {
458				params.width = 0;
459			}
460			try {
461				params.height = Integer.parseInt(parts[2]);
462			} catch (NumberFormatException e) {
463				params.height = 0;
464			}
465		} else if (parts.length == 4) {
466			params.origin = parts[0];
467			try {
468				params.url = new URL(parts[0]);
469			} catch (MalformedURLException e1) {
470				params.url = null;
471			}
472			try {
473				params.size = Long.parseLong(parts[1]);
474			} catch (NumberFormatException e) {
475				params.size = 0;
476			}
477			try {
478				params.width = Integer.parseInt(parts[2]);
479			} catch (NumberFormatException e) {
480				params.width = 0;
481			}
482			try {
483				params.height = Integer.parseInt(parts[3]);
484			} catch (NumberFormatException e) {
485				params.height = 0;
486			}
487		}
488		return params;
489	}
490
491	public ImageParams getLegacyImageParams() {
492		ImageParams params = new ImageParams();
493		if (body == null) {
494			return params;
495		}
496		String parts[] = body.split(",");
497		if (parts.length == 3) {
498			try {
499				params.size = Long.parseLong(parts[0]);
500			} catch (NumberFormatException e) {
501				return null;
502			}
503			try {
504				params.width = Integer.parseInt(parts[1]);
505			} catch (NumberFormatException e) {
506				return null;
507			}
508			try {
509				params.height = Integer.parseInt(parts[2]);
510			} catch (NumberFormatException e) {
511				return null;
512			}
513			return params;
514		} else {
515			return null;
516		}
517	}
518
519	public class ImageParams {
520		public URL url;
521		public long size = 0;
522		public int width = 0;
523		public int height = 0;
524		public String origin;
525	}
526}