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