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