1package eu.siacs.conversations.entities;
2
3import java.security.interfaces.DSAPublicKey;
4import java.util.ArrayList;
5import java.util.List;
6
7import org.json.JSONException;
8import org.json.JSONObject;
9
10import eu.siacs.conversations.services.XmppConnectionService;
11
12import net.java.otr4j.OtrException;
13import net.java.otr4j.crypto.OtrCryptoEngineImpl;
14import net.java.otr4j.crypto.OtrCryptoException;
15import net.java.otr4j.session.SessionID;
16import net.java.otr4j.session.SessionImpl;
17import net.java.otr4j.session.SessionStatus;
18import android.content.ContentValues;
19import android.database.Cursor;
20import android.os.SystemClock;
21
22public class Conversation extends AbstractEntity {
23 public static final String TABLENAME = "conversations";
24
25 public static final int STATUS_AVAILABLE = 0;
26 public static final int STATUS_ARCHIVED = 1;
27 public static final int STATUS_DELETED = 2;
28
29 public static final int MODE_MULTI = 1;
30 public static final int MODE_SINGLE = 0;
31
32 public static final String NAME = "name";
33 public static final String ACCOUNT = "accountUuid";
34 public static final String CONTACT = "contactUuid";
35 public static final String CONTACTJID = "contactJid";
36 public static final String STATUS = "status";
37 public static final String CREATED = "created";
38 public static final String MODE = "mode";
39 public static final String ATTRIBUTES = "attributes";
40
41 public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption";
42 public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
43 public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
44
45 private String name;
46 private String contactUuid;
47 private String accountUuid;
48 private String contactJid;
49 private int status;
50 private long created;
51 private int mode;
52
53 private JSONObject attributes = new JSONObject();
54
55 private String nextPresence;
56
57 protected ArrayList<Message> messages = new ArrayList<Message>();
58 protected Account account = null;
59
60 private transient SessionImpl otrSession;
61
62 private transient String otrFingerprint = null;
63
64 private String nextMessage;
65
66 private transient MucOptions mucOptions = null;
67
68 // private transient String latestMarkableMessageId;
69
70 private byte[] symmetricKey;
71
72 private Bookmark bookmark;
73
74 public Conversation(String name, Account account, String contactJid,
75 int mode) {
76 this(java.util.UUID.randomUUID().toString(), name, null, account
77 .getUuid(), contactJid, System.currentTimeMillis(),
78 STATUS_AVAILABLE, mode, "");
79 this.account = account;
80 }
81
82 public Conversation(String uuid, String name, String contactUuid,
83 String accountUuid, String contactJid, long created, int status,
84 int mode, String attributes) {
85 this.uuid = uuid;
86 this.name = name;
87 this.contactUuid = contactUuid;
88 this.accountUuid = accountUuid;
89 this.contactJid = contactJid;
90 this.created = created;
91 this.status = status;
92 this.mode = mode;
93 try {
94 if (attributes == null) {
95 attributes = new String();
96 }
97 this.attributes = new JSONObject(attributes);
98 } catch (JSONException e) {
99 this.attributes = new JSONObject();
100 }
101 }
102
103 public List<Message> getMessages() {
104 return messages;
105 }
106
107 public boolean isRead() {
108 if ((this.messages == null) || (this.messages.size() == 0))
109 return true;
110 return this.messages.get(this.messages.size() - 1).isRead();
111 }
112
113 public void markRead() {
114 if (this.messages == null) {
115 return;
116 }
117 for (int i = this.messages.size() - 1; i >= 0; --i) {
118 if (messages.get(i).isRead()) {
119 break;
120 }
121 this.messages.get(i).markRead();
122 }
123 }
124
125 public String getLatestMarkableMessageId() {
126 if (this.messages == null) {
127 return null;
128 }
129 for (int i = this.messages.size() - 1; i >= 0; --i) {
130 if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
131 && this.messages.get(i).markable) {
132 if (this.messages.get(i).isRead()) {
133 return null;
134 } else {
135 return this.messages.get(i).getRemoteMsgId();
136 }
137 }
138 }
139 return null;
140 }
141
142 public Message getLatestMessage() {
143 if ((this.messages == null) || (this.messages.size() == 0)) {
144 Message message = new Message(this, "", Message.ENCRYPTION_NONE);
145 message.setTime(getCreated());
146 return message;
147 } else {
148 Message message = this.messages.get(this.messages.size() - 1);
149 message.setConversation(this);
150 return message;
151 }
152 }
153
154 public void setMessages(ArrayList<Message> msgs) {
155 this.messages = msgs;
156 }
157
158 public String getName() {
159 if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
160 return getMucOptions().getSubject();
161 } else if (getMode() == MODE_MULTI && bookmark != null
162 && bookmark.getName() != null) {
163 return bookmark.getName();
164 } else {
165 return this.getContact().getDisplayName();
166 }
167 }
168
169 public String getProfilePhotoString() {
170 return this.getContact().getProfilePhoto();
171 }
172
173 public String getAccountUuid() {
174 return this.accountUuid;
175 }
176
177 public Account getAccount() {
178 return this.account;
179 }
180
181 public Contact getContact() {
182 return this.account.getRoster().getContact(this.contactJid);
183 }
184
185 public void setAccount(Account account) {
186 this.account = account;
187 }
188
189 public String getContactJid() {
190 return this.contactJid;
191 }
192
193 public int getStatus() {
194 return this.status;
195 }
196
197 public long getCreated() {
198 return this.created;
199 }
200
201 public ContentValues getContentValues() {
202 ContentValues values = new ContentValues();
203 values.put(UUID, uuid);
204 values.put(NAME, name);
205 values.put(CONTACT, contactUuid);
206 values.put(ACCOUNT, accountUuid);
207 values.put(CONTACTJID, contactJid);
208 values.put(CREATED, created);
209 values.put(STATUS, status);
210 values.put(MODE, mode);
211 values.put(ATTRIBUTES, attributes.toString());
212 return values;
213 }
214
215 public static Conversation fromCursor(Cursor cursor) {
216 return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
217 cursor.getString(cursor.getColumnIndex(NAME)),
218 cursor.getString(cursor.getColumnIndex(CONTACT)),
219 cursor.getString(cursor.getColumnIndex(ACCOUNT)),
220 cursor.getString(cursor.getColumnIndex(CONTACTJID)),
221 cursor.getLong(cursor.getColumnIndex(CREATED)),
222 cursor.getInt(cursor.getColumnIndex(STATUS)),
223 cursor.getInt(cursor.getColumnIndex(MODE)),
224 cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
225 }
226
227 public void setStatus(int status) {
228 this.status = status;
229 }
230
231 public int getMode() {
232 return this.mode;
233 }
234
235 public void setMode(int mode) {
236 this.mode = mode;
237 }
238
239 public SessionImpl startOtrSession(XmppConnectionService service,
240 String presence, boolean sendStart) {
241 if (this.otrSession != null) {
242 return this.otrSession;
243 } else {
244 SessionID sessionId = new SessionID(this.getContactJid().split("/",
245 2)[0], presence, "xmpp");
246 this.otrSession = new SessionImpl(sessionId, getAccount()
247 .getOtrEngine(service));
248 try {
249 if (sendStart) {
250 this.otrSession.startSession();
251 return this.otrSession;
252 }
253 return this.otrSession;
254 } catch (OtrException e) {
255 return null;
256 }
257 }
258
259 }
260
261 public SessionImpl getOtrSession() {
262 return this.otrSession;
263 }
264
265 public void resetOtrSession() {
266 this.otrFingerprint = null;
267 this.otrSession = null;
268 }
269
270 public void startOtrIfNeeded() {
271 if (this.otrSession != null
272 && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
273 try {
274 this.otrSession.startSession();
275 } catch (OtrException e) {
276 this.resetOtrSession();
277 }
278 }
279 }
280
281 public boolean endOtrIfNeeded() {
282 if (this.otrSession != null) {
283 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
284 try {
285 this.otrSession.endSession();
286 this.resetOtrSession();
287 return true;
288 } catch (OtrException e) {
289 this.resetOtrSession();
290 return false;
291 }
292 } else {
293 this.resetOtrSession();
294 return false;
295 }
296 } else {
297 return false;
298 }
299 }
300
301 public boolean hasValidOtrSession() {
302 return this.otrSession != null;
303 }
304
305 public String getOtrFingerprint() {
306 if (this.otrFingerprint == null) {
307 try {
308 if (getOtrSession() == null) {
309 return "";
310 }
311 DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession()
312 .getRemotePublicKey();
313 StringBuilder builder = new StringBuilder(
314 new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
315 builder.insert(8, " ");
316 builder.insert(17, " ");
317 builder.insert(26, " ");
318 builder.insert(35, " ");
319 this.otrFingerprint = builder.toString();
320 } catch (OtrCryptoException e) {
321
322 }
323 }
324 return this.otrFingerprint;
325 }
326
327 public synchronized MucOptions getMucOptions() {
328 if (this.mucOptions == null) {
329 this.mucOptions = new MucOptions(this);
330 }
331 return this.mucOptions;
332 }
333
334 public void resetMucOptions() {
335 this.mucOptions = null;
336 }
337
338 public void setContactJid(String jid) {
339 this.contactJid = jid;
340 }
341
342 public void setNextPresence(String presence) {
343 this.nextPresence = presence;
344 }
345
346 public String getNextPresence() {
347 return this.nextPresence;
348 }
349
350 public int getLatestEncryption() {
351 int latestEncryption = this.getLatestMessage().getEncryption();
352 if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
353 || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
354 return Message.ENCRYPTION_PGP;
355 } else {
356 return latestEncryption;
357 }
358 }
359
360 public int getNextEncryption(boolean force) {
361 int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
362 if (next == -1) {
363 int latest = this.getLatestEncryption();
364 if (latest == Message.ENCRYPTION_NONE) {
365 if (force && getMode() == MODE_SINGLE) {
366 return Message.ENCRYPTION_OTR;
367 } else if (getContact().getPresences().size() == 1) {
368 if (getContact().getOtrFingerprints().size() >= 1) {
369 return Message.ENCRYPTION_OTR;
370 } else {
371 return latest;
372 }
373 } else {
374 return latest;
375 }
376 } else {
377 return latest;
378 }
379 }
380 if (next == Message.ENCRYPTION_NONE && force
381 && getMode() == MODE_SINGLE) {
382 return Message.ENCRYPTION_OTR;
383 } else {
384 return next;
385 }
386 }
387
388 public void setNextEncryption(int encryption) {
389 this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
390 }
391
392 public String getNextMessage() {
393 if (this.nextMessage == null) {
394 return "";
395 } else {
396 return this.nextMessage;
397 }
398 }
399
400 public void setNextMessage(String message) {
401 this.nextMessage = message;
402 }
403
404 public void setSymmetricKey(byte[] key) {
405 this.symmetricKey = key;
406 }
407
408 public byte[] getSymmetricKey() {
409 return this.symmetricKey;
410 }
411
412 public void setBookmark(Bookmark bookmark) {
413 this.bookmark = bookmark;
414 this.bookmark.setConversation(this);
415 }
416
417 public void deregisterWithBookmark() {
418 if (this.bookmark != null) {
419 this.bookmark.setConversation(null);
420 }
421 }
422
423 public Bookmark getBookmark() {
424 return this.bookmark;
425 }
426
427 public boolean hasDuplicateMessage(Message message) {
428 for (int i = this.getMessages().size() - 1; i >= 0; --i) {
429 if (this.messages.get(i).equals(message)) {
430 return true;
431 }
432 }
433 return false;
434 }
435
436 public void setMutedTill(long value) {
437 this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
438 }
439
440 public boolean isMuted() {
441 return SystemClock.elapsedRealtime() < this.getLongAttribute(
442 ATTRIBUTE_MUTED_TILL, 0);
443 }
444
445 public boolean setAttribute(String key, String value) {
446 try {
447 this.attributes.put(key, value);
448 return true;
449 } catch (JSONException e) {
450 return false;
451 }
452 }
453
454 public String getAttribute(String key) {
455 try {
456 return this.attributes.getString(key);
457 } catch (JSONException e) {
458 return null;
459 }
460 }
461
462 public int getIntAttribute(String key, int defaultValue) {
463 String value = this.getAttribute(key);
464 if (value == null) {
465 return defaultValue;
466 } else {
467 try {
468 return Integer.parseInt(value);
469 } catch (NumberFormatException e) {
470 return defaultValue;
471 }
472 }
473 }
474
475 public long getLongAttribute(String key, long defaultValue) {
476 String value = this.getAttribute(key);
477 if (value == null) {
478 return defaultValue;
479 } else {
480 try {
481 return Long.parseLong(value);
482 } catch (NumberFormatException e) {
483 return defaultValue;
484 }
485 }
486 }
487
488 public void add(Message message) {
489 message.setConversation(this);
490 synchronized (this.messages) {
491 this.messages.add(message);
492 }
493 }
494
495 public void addAll(int index, List<Message> messages) {
496 synchronized (this.messages) {
497 this.messages.addAll(index, messages);
498 }
499 }
500}