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