Message.java

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