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