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 String getPresence() {
252		if (!counterpart.getResourcepart().isEmpty()) {
253			return counterpart.getResourcepart();
254		} else {
255            // TODO: Return empty string or null?
256			return null;
257		}
258	}
259
260	public void setDownloadable(Downloadable downloadable) {
261		this.downloadable = downloadable;
262	}
263
264	public Downloadable getDownloadable() {
265		return this.downloadable;
266	}
267
268	public static Message createStatusMessage(Conversation conversation) {
269		Message message = new Message();
270		message.setType(Message.TYPE_STATUS);
271		message.setConversation(conversation);
272		return message;
273	}
274
275	public void setCounterpart(final Jid counterpart) {
276		this.counterpart = counterpart;
277	}
278
279	public boolean equals(Message message) {
280		if ((this.remoteMsgId != null) && (this.body != null)
281				&& (this.counterpart != null)) {
282			return this.remoteMsgId.equals(message.getRemoteMsgId())
283					&& this.body.equals(message.getBody())
284					&& this.counterpart.equals(message.getCounterpart());
285		} else {
286			return false;
287		}
288	}
289
290	public Message next() {
291		if (this.mNextMessage == null) {
292			synchronized (this.conversation.messages) {
293				int index = this.conversation.messages.indexOf(this);
294				if (index < 0
295						|| index >= this.conversation.getMessages().size() - 1) {
296					this.mNextMessage = null;
297				} else {
298					this.mNextMessage = this.conversation.messages
299							.get(index + 1);
300				}
301			}
302		}
303		return this.mNextMessage;
304	}
305
306	public Message prev() {
307		if (this.mPreviousMessage == null) {
308			synchronized (this.conversation.messages) {
309				int index = this.conversation.messages.indexOf(this);
310				if (index <= 0 || index > this.conversation.messages.size()) {
311					this.mPreviousMessage = null;
312				} else {
313					this.mPreviousMessage = this.conversation.messages
314							.get(index - 1);
315				}
316			}
317		}
318		return this.mPreviousMessage;
319	}
320
321	public boolean mergeable(Message message) {
322		if (message == null) {
323			return false;
324		}
325		return (message.getType() == Message.TYPE_TEXT
326				&& this.getDownloadable() == null
327				&& message.getDownloadable() == null
328				&& message.getEncryption() != Message.ENCRYPTION_PGP
329				&& this.getType() == message.getType()
330				&& this.getEncryption() == message.getEncryption()
331				&& this.getCounterpart().equals(message.getCounterpart())
332				&& (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
333				.getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
334				.getStatus() == Message.STATUS_SEND_RECEIVED) && (message
335				.getStatus() == Message.STATUS_UNSEND
336				|| message.getStatus() == Message.STATUS_SEND || message
337					.getStatus() == Message.STATUS_SEND_DISPLAYED))))
338				&& !message.bodyContainsDownloadable()
339				&& !this.bodyContainsDownloadable());
340	}
341
342	public String getMergedBody() {
343		Message next = this.next();
344		if (this.mergeable(next)) {
345			return body.trim() + '\n' + next.getMergedBody();
346		}
347		return body.trim();
348	}
349
350	public int getMergedStatus() {
351		Message next = this.next();
352		if (this.mergeable(next)) {
353			return next.getMergedStatus();
354		} else {
355			return getStatus();
356		}
357	}
358
359	public long getMergedTimeSent() {
360		Message next = this.next();
361		if (this.mergeable(next)) {
362			return next.getMergedTimeSent();
363		} else {
364			return getTimeSent();
365		}
366	}
367
368	public boolean wasMergedIntoPrevious() {
369		Message prev = this.prev();
370        return prev != null && prev.mergeable(this);
371	}
372	
373	public boolean trusted() {
374		Contact contact = this.getContact();
375		return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
376	}
377
378	public boolean bodyContainsDownloadable() {
379		try {
380			URL url = new URL(this.getBody());
381			if (!url.getProtocol().equalsIgnoreCase("http")
382					&& !url.getProtocol().equalsIgnoreCase("https")) {
383				return false;
384			}
385			if (url.getPath() == null) {
386				return false;
387			}
388			String[] pathParts = url.getPath().split("/");
389			String filename;
390			if (pathParts.length > 0) {
391				filename = pathParts[pathParts.length - 1];
392			} else {
393				return false;
394			}
395			String[] extensionParts = filename.split("\\.");
396			if (extensionParts.length == 2
397					&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
398							extensionParts[extensionParts.length - 1])) {
399				return true;
400			} else if (extensionParts.length == 3
401					&& Arrays
402							.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
403							.contains(extensionParts[extensionParts.length - 1])
404					&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
405							extensionParts[extensionParts.length - 2])) {
406				return true;
407			} else {
408				return false;
409			}
410		} catch (MalformedURLException e) {
411			return false;
412		}
413	}
414
415	public ImageParams getImageParams() {
416		ImageParams params = getLegacyImageParams();
417		if (params!=null) {
418			return params;
419		}
420		params = new ImageParams();
421		if (this.downloadable != null) {
422			params.size = this.downloadable.getFileSize();
423		}
424		if (body == null) {
425			return params;
426		}
427		String parts[] = body.split("\\|");
428		if (parts.length == 1) {
429			try {
430				params.size = Long.parseLong(parts[0]);
431			} catch (NumberFormatException e) {
432				params.origin = parts[0];
433				try {
434					params.url = new URL(parts[0]);
435				} catch (MalformedURLException e1) {
436					params.url = null;
437				}
438			}
439		} else if (parts.length == 3) {
440			try {
441				params.size = Long.parseLong(parts[0]);
442			} catch (NumberFormatException e) {
443				params.size = 0;
444			}
445			try {
446				params.width = Integer.parseInt(parts[1]);
447			} catch (NumberFormatException e) {
448				params.width = 0;
449			}
450			try {
451				params.height = Integer.parseInt(parts[2]);
452			} catch (NumberFormatException e) {
453				params.height = 0;
454			}
455		} else if (parts.length == 4) {
456			params.origin = parts[0];
457			try {
458				params.url = new URL(parts[0]);
459			} catch (MalformedURLException e1) {
460				params.url = null;
461			}
462			try {
463				params.size = Long.parseLong(parts[1]);
464			} catch (NumberFormatException e) {
465				params.size = 0;
466			}
467			try {
468				params.width = Integer.parseInt(parts[2]);
469			} catch (NumberFormatException e) {
470				params.width = 0;
471			}
472			try {
473				params.height = Integer.parseInt(parts[3]);
474			} catch (NumberFormatException e) {
475				params.height = 0;
476			}
477		}
478		return params;
479	}
480	
481	public ImageParams getLegacyImageParams() {
482		ImageParams params = new ImageParams();
483		if (body == null) {
484			return params;
485		}
486		String parts[] = body.split(",");
487		if (parts.length == 3) {
488			try {
489				params.size = Long.parseLong(parts[0]);
490			} catch (NumberFormatException e) {
491				return null;
492			}
493			try {
494				params.width = Integer.parseInt(parts[1]);
495			} catch (NumberFormatException e) {
496				return null;
497			}
498			try {
499				params.height = Integer.parseInt(parts[2]);
500			} catch (NumberFormatException e) {
501				return null;
502			}
503			return params;
504		} else {
505			return null;
506		}
507	}
508
509	public class ImageParams {
510		public URL url;
511		public long size = 0;
512		public int width = 0;
513		public int height = 0;
514		public String origin;
515	}
516}