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