Message.java

  1package eu.siacs.conversations.entities;
  2
  3import java.net.MalformedURLException;
  4import java.net.URL;
  5import java.util.Arrays;
  6
  7import eu.siacs.conversations.Config;
  8import android.content.ContentValues;
  9import android.database.Cursor;
 10import android.util.Log;
 11
 12public class Message extends AbstractEntity {
 13
 14	public static final String TABLENAME = "messages";
 15
 16	public static final int STATUS_RECEIVED = 0;
 17	public static final int STATUS_UNSEND = 1;
 18	public static final int STATUS_SEND = 2;
 19	public static final int STATUS_SEND_FAILED = 3;
 20	public static final int STATUS_SEND_REJECTED = 4;
 21	public static final int STATUS_WAITING = 5;
 22	public static final int STATUS_OFFERED = 6;
 23	public static final int STATUS_SEND_RECEIVED = 7;
 24	public static final int STATUS_SEND_DISPLAYED = 8;
 25
 26	public static final int ENCRYPTION_NONE = 0;
 27	public static final int ENCRYPTION_PGP = 1;
 28	public static final int ENCRYPTION_OTR = 2;
 29	public static final int ENCRYPTION_DECRYPTED = 3;
 30	public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
 31
 32	public static final int TYPE_TEXT = 0;
 33	public static final int TYPE_IMAGE = 1;
 34	public static final int TYPE_AUDIO = 2;
 35	public static final int TYPE_STATUS = 3;
 36	public static final int TYPE_PRIVATE = 4;
 37
 38	public static String CONVERSATION = "conversationUuid";
 39	public static String COUNTERPART = "counterpart";
 40	public static String TRUE_COUNTERPART = "trueCounterpart";
 41	public static String BODY = "body";
 42	public static String TIME_SENT = "timeSent";
 43	public static String ENCRYPTION = "encryption";
 44	public static String STATUS = "status";
 45	public static String TYPE = "type";
 46	public static String REMOTE_MSG_ID = "remoteMsgId";
 47
 48	protected String conversationUuid;
 49	protected String counterpart;
 50	protected String trueCounterpart;
 51	protected String body;
 52	protected String encryptedBody;
 53	protected long timeSent;
 54	protected int encryption;
 55	protected int status;
 56	protected int type;
 57	protected boolean read = true;
 58	protected String remoteMsgId = null;
 59
 60	protected Conversation conversation = null;
 61	protected Downloadable downloadable = null;
 62	public boolean markable = false;
 63
 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(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
 73				conversation.getContactJid(), null, body, System
 74						.currentTimeMillis(), encryption,
 75				Message.STATUS_UNSEND, TYPE_TEXT, null);
 76		this.conversation = conversation;
 77	}
 78
 79	public Message(Conversation conversation, String counterpart, String body,
 80			int encryption, int status) {
 81		this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
 82				counterpart, null, body, System.currentTimeMillis(),
 83				encryption, status, TYPE_TEXT, null);
 84		this.conversation = conversation;
 85	}
 86
 87	public Message(String uuid, String conversationUUid, String counterpart,
 88			String trueCounterpart, String body, long timeSent, int encryption,
 89			int status, int type, String remoteMsgId) {
 90		this.uuid = uuid;
 91		this.conversationUuid = conversationUUid;
 92		this.counterpart = counterpart;
 93		this.trueCounterpart = trueCounterpart;
 94		this.body = body;
 95		this.timeSent = timeSent;
 96		this.encryption = encryption;
 97		this.status = status;
 98		this.type = type;
 99		this.remoteMsgId = remoteMsgId;
100	}
101
102	@Override
103	public ContentValues getContentValues() {
104		ContentValues values = new ContentValues();
105		values.put(UUID, uuid);
106		values.put(CONVERSATION, conversationUuid);
107		values.put(COUNTERPART, counterpart);
108		values.put(TRUE_COUNTERPART, trueCounterpart);
109		values.put(BODY, body);
110		values.put(TIME_SENT, timeSent);
111		values.put(ENCRYPTION, encryption);
112		values.put(STATUS, status);
113		values.put(TYPE, type);
114		values.put(REMOTE_MSG_ID, remoteMsgId);
115		return values;
116	}
117
118	public String getConversationUuid() {
119		return conversationUuid;
120	}
121
122	public Conversation getConversation() {
123		return this.conversation;
124	}
125
126	public String getCounterpart() {
127		return counterpart;
128	}
129
130	public Contact getContact() {
131		if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
132			return this.conversation.getContact();
133		} else {
134			if (this.trueCounterpart == null) {
135				return null;
136			} else {
137				return this.conversation.getAccount().getRoster()
138						.getContactFromRoster(this.trueCounterpart);
139			}
140		}
141	}
142
143	public String getBody() {
144		return body;
145	}
146
147	public long getTimeSent() {
148		return timeSent;
149	}
150
151	public int getEncryption() {
152		return encryption;
153	}
154
155	public int getStatus() {
156		return status;
157	}
158
159	public String getRemoteMsgId() {
160		return this.remoteMsgId;
161	}
162
163	public void setRemoteMsgId(String id) {
164		this.remoteMsgId = id;
165	}
166
167	public static Message fromCursor(Cursor cursor) {
168		return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
169				cursor.getString(cursor.getColumnIndex(CONVERSATION)),
170				cursor.getString(cursor.getColumnIndex(COUNTERPART)),
171				cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)),
172				cursor.getString(cursor.getColumnIndex(BODY)),
173				cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
174				cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
175				cursor.getInt(cursor.getColumnIndex(STATUS)),
176				cursor.getInt(cursor.getColumnIndex(TYPE)),
177				cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)));
178	}
179
180	public void setConversation(Conversation conv) {
181		this.conversation = conv;
182	}
183
184	public void setStatus(int status) {
185		this.status = status;
186	}
187
188	public boolean isRead() {
189		return this.read;
190	}
191
192	public void markRead() {
193		this.read = true;
194	}
195
196	public void markUnread() {
197		this.read = false;
198	}
199
200	public void setTime(long time) {
201		this.timeSent = time;
202	}
203
204	public void setEncryption(int encryption) {
205		this.encryption = encryption;
206	}
207
208	public void setBody(String body) {
209		this.body = body;
210	}
211
212	public String getEncryptedBody() {
213		return this.encryptedBody;
214	}
215
216	public void setEncryptedBody(String body) {
217		this.encryptedBody = body;
218	}
219
220	public void setType(int type) {
221		this.type = type;
222	}
223
224	public int getType() {
225		return this.type;
226	}
227
228	public void setPresence(String presence) {
229		if (presence == null) {
230			this.counterpart = this.counterpart.split("/", 2)[0];
231		} else {
232			this.counterpart = this.counterpart.split("/", 2)[0] + "/"
233					+ presence;
234		}
235	}
236
237	public void setTrueCounterpart(String trueCounterpart) {
238		this.trueCounterpart = trueCounterpart;
239	}
240
241	public String getPresence() {
242		String[] counterparts = this.counterpart.split("/", 2);
243		if (counterparts.length == 2) {
244			return counterparts[1];
245		} else {
246			if (this.counterpart.contains("/")) {
247				return "";
248			} else {
249				return null;
250			}
251		}
252	}
253
254	public void setDownloadable(Downloadable downloadable) {
255		this.downloadable = downloadable;
256	}
257
258	public Downloadable getDownloadable() {
259		return this.downloadable;
260	}
261
262	public static Message createStatusMessage(Conversation conversation) {
263		Message message = new Message();
264		message.setType(Message.TYPE_STATUS);
265		message.setConversation(conversation);
266		return message;
267	}
268
269	public void setCounterpart(String counterpart) {
270		this.counterpart = counterpart;
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 mergable(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().equals(message.getCounterpart())
326				&& (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
327				.getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
328				.getStatus() == Message.STATUS_SEND_RECEIVED) && (message
329				.getStatus() == Message.STATUS_UNSEND
330				|| message.getStatus() == Message.STATUS_SEND || message
331					.getStatus() == Message.STATUS_SEND_DISPLAYED))))
332				&& !message.bodyContainsDownloadable()
333				&& !this.bodyContainsDownloadable());
334	}
335
336	public String getMergedBody() {
337		Message next = this.next();
338		if (this.mergable(next)) {
339			return body.trim() + '\n' + next.getMergedBody();
340		}
341		return body.trim();
342	}
343
344	public int getMergedStatus() {
345		Message next = this.next();
346		if (this.mergable(next)) {
347			return next.getMergedStatus();
348		} else {
349			return getStatus();
350		}
351	}
352
353	public long getMergedTimeSent() {
354		Message next = this.next();
355		if (this.mergable(next)) {
356			return next.getMergedTimeSent();
357		} else {
358			return getTimeSent();
359		}
360	}
361
362	public boolean wasMergedIntoPrevious() {
363		Message prev = this.prev();
364		if (prev == null) {
365			return false;
366		} else {
367			return prev.mergable(this);
368		}
369	}
370
371	public boolean bodyContainsDownloadable() {
372		Contact contact = this.getContact();
373		if (status <= STATUS_RECEIVED
374				&& (contact == null || !contact.trusted())) {
375			return false;
376		}
377		try {
378			URL url = new URL(this.getBody());
379			if (!url.getProtocol().equalsIgnoreCase("http")
380					&& !url.getProtocol().equalsIgnoreCase("https")) {
381				return false;
382			}
383			if (url.getPath() == null) {
384				return false;
385			}
386			String[] pathParts = url.getPath().split("/");
387			String filename = pathParts[pathParts.length - 1];
388			String[] extensionParts = filename.split("\\.");
389			if (extensionParts.length == 2
390					&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
391							extensionParts[extensionParts.length - 1])) {
392				return true;
393			} else if (extensionParts.length == 3
394					&& Arrays
395							.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
396							.contains(extensionParts[extensionParts.length - 1])
397					&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
398							extensionParts[extensionParts.length - 2])) {
399				return true;
400			} else {
401				return false;
402			}
403		} catch (MalformedURLException e) {
404			return false;
405		}
406	}
407
408	public ImageParams getImageParams() {
409		ImageParams params = getLegacyImageParams();
410		if (params!=null) {
411			return params;
412		}
413		params = new ImageParams();
414		if (this.downloadable != null) {
415			params.size = this.downloadable.getFileSize();
416		}
417		if (body == null) {
418			return params;
419		}
420		String parts[] = body.split("\\|");
421		if (parts.length == 1) {
422			try {
423				params.size = Long.parseLong(parts[0]);
424			} catch (NumberFormatException e) {
425				params.origin = parts[0];
426				try {
427					params.url = new URL(parts[0]);
428				} catch (MalformedURLException e1) {
429					params.url = null;
430				}
431			}
432		} else if (parts.length == 3) {
433			try {
434				params.size = Long.parseLong(parts[0]);
435			} catch (NumberFormatException e) {
436				params.size = 0;
437			}
438			try {
439				params.width = Integer.parseInt(parts[1]);
440			} catch (NumberFormatException e) {
441				params.width = 0;
442			}
443			try {
444				params.height = Integer.parseInt(parts[2]);
445			} catch (NumberFormatException e) {
446				params.height = 0;
447			}
448		} else if (parts.length == 4) {
449			params.origin = parts[0];
450			try {
451				params.url = new URL(parts[0]);
452			} catch (MalformedURLException e1) {
453				params.url = null;
454			}
455			try {
456				params.size = Long.parseLong(parts[1]);
457			} catch (NumberFormatException e) {
458				params.size = 0;
459			}
460			try {
461				params.width = Integer.parseInt(parts[2]);
462			} catch (NumberFormatException e) {
463				params.width = 0;
464			}
465			try {
466				params.height = Integer.parseInt(parts[3]);
467			} catch (NumberFormatException e) {
468				params.height = 0;
469			}
470		}
471		return params;
472	}
473	
474	public ImageParams getLegacyImageParams() {
475		ImageParams params = new ImageParams();
476		if (body == null) {
477			return params;
478		}
479		String parts[] = body.split(",");
480		if (parts.length == 3) {
481			try {
482				params.size = Long.parseLong(parts[0]);
483			} catch (NumberFormatException e) {
484				return null;
485			}
486			try {
487				params.width = Integer.parseInt(parts[1]);
488			} catch (NumberFormatException e) {
489				return null;
490			}
491			try {
492				params.height = Integer.parseInt(parts[2]);
493			} catch (NumberFormatException e) {
494				return null;
495			}
496			return params;
497		} else {
498			return null;
499		}
500	}
501
502	public class ImageParams {
503		public URL url;
504		public long size = 0;
505		public int width = 0;
506		public int height = 0;
507		public String origin;
508	}
509}