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