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