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