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