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