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;
12import android.text.InputFilter.LengthFilter;
13
14public class Message extends AbstractEntity {
15
16 public static final String TABLENAME = "messages";
17
18 public static final int STATUS_RECEPTION_FAILED = -3;
19 public static final int STATUS_RECEIVED_OFFER = -2;
20 public static final int STATUS_RECEIVING = -1;
21 public static final int STATUS_RECEIVED = 0;
22 public static final int STATUS_UNSEND = 1;
23 public static final int STATUS_SEND = 2;
24 public static final int STATUS_SEND_FAILED = 3;
25 public static final int STATUS_SEND_REJECTED = 4;
26 public static final int STATUS_WAITING = 5;
27 public static final int STATUS_OFFERED = 6;
28 public static final int STATUS_SEND_RECEIVED = 7;
29 public static final int STATUS_SEND_DISPLAYED = 8;
30
31 public static final int ENCRYPTION_NONE = 0;
32 public static final int ENCRYPTION_PGP = 1;
33 public static final int ENCRYPTION_OTR = 2;
34 public static final int ENCRYPTION_DECRYPTED = 3;
35 public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
36
37 public static final int TYPE_TEXT = 0;
38 public static final int TYPE_IMAGE = 1;
39 public static final int TYPE_AUDIO = 2;
40 public static final int TYPE_STATUS = 3;
41 public static final int TYPE_PRIVATE = 4;
42
43 public static String CONVERSATION = "conversationUuid";
44 public static String COUNTERPART = "counterpart";
45 public static String TRUE_COUNTERPART = "trueCounterpart";
46 public static String BODY = "body";
47 public static String TIME_SENT = "timeSent";
48 public static String ENCRYPTION = "encryption";
49 public static String STATUS = "status";
50 public static String TYPE = "type";
51 public static String REMOTE_MSG_ID = "remoteMsgId";
52
53 protected String conversationUuid;
54 protected String counterpart;
55 protected String trueCounterpart;
56 protected String body;
57 protected String encryptedBody;
58 protected long timeSent;
59 protected int encryption;
60 protected int status;
61 protected int type;
62 protected boolean read = true;
63 protected String remoteMsgId = null;
64
65 protected Conversation conversation = null;
66 protected Downloadable downloadable = null;
67 public boolean markable = false;
68
69 private Message() {
70
71 }
72
73 public Message(Conversation conversation, String body, int encryption) {
74 this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
75 conversation.getContactJid(), null, body, System
76 .currentTimeMillis(), encryption,
77 Message.STATUS_UNSEND, TYPE_TEXT, null);
78 this.conversation = conversation;
79 }
80
81 public Message(Conversation conversation, String counterpart, String body,
82 int encryption, int status) {
83 this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
84 counterpart, null, body, System.currentTimeMillis(),
85 encryption, status, TYPE_TEXT, null);
86 this.conversation = conversation;
87 }
88
89 public Message(String uuid, String conversationUUid, String counterpart,
90 String trueCounterpart, String body, long timeSent, int encryption,
91 int status, int type, String remoteMsgId) {
92 this.uuid = uuid;
93 this.conversationUuid = conversationUUid;
94 this.counterpart = counterpart;
95 this.trueCounterpart = trueCounterpart;
96 this.body = body;
97 this.timeSent = timeSent;
98 this.encryption = encryption;
99 this.status = status;
100 this.type = type;
101 this.remoteMsgId = remoteMsgId;
102 }
103
104 @Override
105 public ContentValues getContentValues() {
106 ContentValues values = new ContentValues();
107 values.put(UUID, uuid);
108 values.put(CONVERSATION, conversationUuid);
109 values.put(COUNTERPART, counterpart);
110 values.put(TRUE_COUNTERPART, trueCounterpart);
111 values.put(BODY, body);
112 values.put(TIME_SENT, timeSent);
113 values.put(ENCRYPTION, encryption);
114 values.put(STATUS, status);
115 values.put(TYPE, type);
116 values.put(REMOTE_MSG_ID, remoteMsgId);
117 return values;
118 }
119
120 public String getConversationUuid() {
121 return conversationUuid;
122 }
123
124 public Conversation getConversation() {
125 return this.conversation;
126 }
127
128 public String getCounterpart() {
129 return counterpart;
130 }
131
132 public Contact getContact() {
133 if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
134 return this.conversation.getContact();
135 } else {
136 if (this.trueCounterpart == null) {
137 return null;
138 } else {
139 return this.conversation.getAccount().getRoster().getContactFromRoster(
140 this.trueCounterpart);
141 }
142 }
143 }
144
145 public String getBody() {
146 return body;
147 }
148
149 public String getReadableBody(Context context) {
150 if ((encryption == ENCRYPTION_PGP) && (type == TYPE_TEXT)) {
151 return context.getText(R.string.encrypted_message_received)
152 .toString();
153 } else if ((encryption == ENCRYPTION_OTR) && (type == TYPE_IMAGE)) {
154 return context.getText(R.string.encrypted_image_received)
155 .toString();
156 } else if (encryption == ENCRYPTION_DECRYPTION_FAILED) {
157 return context.getText(R.string.decryption_failed).toString();
158 } else if (type == TYPE_IMAGE) {
159 return context.getText(R.string.image_file).toString();
160 } else {
161 return body.trim();
162 }
163 }
164
165 public long getTimeSent() {
166 return timeSent;
167 }
168
169 public int getEncryption() {
170 return encryption;
171 }
172
173 public int getStatus() {
174 return status;
175 }
176
177 public String getRemoteMsgId() {
178 return this.remoteMsgId;
179 }
180
181 public void setRemoteMsgId(String id) {
182 this.remoteMsgId = id;
183 }
184
185 public static Message fromCursor(Cursor cursor) {
186 return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
187 cursor.getString(cursor.getColumnIndex(CONVERSATION)),
188 cursor.getString(cursor.getColumnIndex(COUNTERPART)),
189 cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)),
190 cursor.getString(cursor.getColumnIndex(BODY)),
191 cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
192 cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
193 cursor.getInt(cursor.getColumnIndex(STATUS)),
194 cursor.getInt(cursor.getColumnIndex(TYPE)),
195 cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)));
196 }
197
198 public void setConversation(Conversation conv) {
199 this.conversation = conv;
200 }
201
202 public void setStatus(int status) {
203 this.status = status;
204 }
205
206 public boolean isRead() {
207 return this.read;
208 }
209
210 public void markRead() {
211 this.read = true;
212 }
213
214 public void markUnread() {
215 this.read = false;
216 }
217
218 public void setTime(long time) {
219 this.timeSent = time;
220 }
221
222 public void setEncryption(int encryption) {
223 this.encryption = encryption;
224 }
225
226 public void setBody(String body) {
227 this.body = body;
228 }
229
230 public String getEncryptedBody() {
231 return this.encryptedBody;
232 }
233
234 public void setEncryptedBody(String body) {
235 this.encryptedBody = body;
236 }
237
238 public void setType(int type) {
239 this.type = type;
240 }
241
242 public int getType() {
243 return this.type;
244 }
245
246 public void setPresence(String presence) {
247 if (presence == null) {
248 this.counterpart = this.counterpart.split("/", 2)[0];
249 } else {
250 this.counterpart = this.counterpart.split("/", 2)[0] + "/"
251 + presence;
252 }
253 }
254
255 public void setTrueCounterpart(String trueCounterpart) {
256 this.trueCounterpart = trueCounterpart;
257 }
258
259 public String getPresence() {
260 String[] counterparts = this.counterpart.split("/", 2);
261 if (counterparts.length == 2) {
262 return counterparts[1];
263 } else {
264 if (this.counterpart.contains("/")) {
265 return "";
266 } else {
267 return null;
268 }
269 }
270 }
271
272 public void setDownloadable(Downloadable downloadable) {
273 this.downloadable = downloadable;
274 }
275
276 public Downloadable getDownloadable() {
277 return this.downloadable;
278 }
279
280 public static Message createStatusMessage(Conversation conversation) {
281 Message message = new Message();
282 message.setType(Message.TYPE_STATUS);
283 message.setConversation(conversation);
284 return message;
285 }
286
287 public void setCounterpart(String counterpart) {
288 this.counterpart = counterpart;
289 }
290
291 public boolean equals(Message message) {
292 if ((this.remoteMsgId != null) && (this.body != null)
293 && (this.counterpart != null)) {
294 return this.remoteMsgId.equals(message.getRemoteMsgId())
295 && this.body.equals(message.getBody())
296 && this.counterpart.equals(message.getCounterpart());
297 } else {
298 return false;
299 }
300 }
301
302 public Message next() {
303 int index = this.conversation.getMessages().indexOf(this);
304 if (index < 0 || index >= this.conversation.getMessages().size() - 1) {
305 return null;
306 } else {
307 return this.conversation.getMessages().get(index + 1);
308 }
309 }
310
311 public Message prev() {
312 int index = this.conversation.getMessages().indexOf(this);
313 if (index <= 0 || index > this.conversation.getMessages().size()) {
314 return null;
315 } else {
316 return this.conversation.getMessages().get(index - 1);
317 }
318 }
319
320 public boolean mergable(Message message) {
321 if (message == null) {
322 return false;
323 }
324 return (message.getType() == Message.TYPE_TEXT
325 && message.getEncryption() != Message.ENCRYPTION_PGP
326 && this.getType() == message.getType()
327 && this.getEncryption() == message.getEncryption()
328 && this.getCounterpart().equals(message.getCounterpart())
329 && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
330 .getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
331 .getStatus() == Message.STATUS_SEND_RECEIVED) && (message
332 .getStatus() == Message.STATUS_UNSEND
333 || message.getStatus() == Message.STATUS_SEND || message
334 .getStatus() == Message.STATUS_SEND_DISPLAYED)))));
335 }
336
337 public String getMergedBody() {
338 Message next = this.next();
339 if (this.mergable(next)) {
340 return body.trim() + '\n' + next.getMergedBody();
341 }
342 return body.trim();
343 }
344
345 public int getMergedStatus() {
346 Message next = this.next();
347 if (this.mergable(next)) {
348 return next.getMergedStatus();
349 } else {
350 return getStatus();
351 }
352 }
353
354 public long getMergedTimeSent() {
355 Message next = this.next();
356 if (this.mergable(next)) {
357 return next.getMergedTimeSent();
358 } else {
359 return getTimeSent();
360 }
361 }
362
363 public boolean wasMergedIntoPrevious() {
364 Message prev = this.prev();
365 if (prev == null) {
366 return false;
367 } else {
368 return prev.mergable(this);
369 }
370 }
371
372 public boolean bodyContainsDownloadable() {
373 Contact contact = this.getContact();
374 if (contact == null || !contact.trusted()) {
375 return false;
376 }
377 try {
378 URL url = new URL(this.getBody());
379 if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
380 return false;
381 }
382 if (url.getPath()==null) {
383 return false;
384 }
385 String[] pathParts = url.getPath().split("/");
386 String filename = pathParts[pathParts.length - 1];
387 String[] extensionParts = filename.split("\\.");
388 if (extensionParts.length == 2 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(extensionParts[extensionParts.length -1])) {
389 return true;
390 } else if (extensionParts.length == 3 && Arrays.asList(Downloadable.VALID_CRYPTO_EXTENSIONS).contains(extensionParts.length -1) && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(extensionParts[extensionParts.length -2])) {
391 return true;
392 } else {
393 return false;
394 }
395 } catch (MalformedURLException e) {
396 return false;
397 }
398 }
399}