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