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