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