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