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 && this.messages.get(i).markable) {
147 if (this.messages.get(i).isRead()) {
148 return null;
149 } else {
150 return this.messages.get(i).getRemoteMsgId();
151 }
152 }
153 }
154 return null;
155 }
156
157 public Message getLatestMessage() {
158 if ((this.messages == null) || (this.messages.size() == 0)) {
159 Message message = new Message(this, "", Message.ENCRYPTION_NONE);
160 message.setTime(getCreated());
161 return message;
162 } else {
163 Message message = this.messages.get(this.messages.size() - 1);
164 message.setConversation(this);
165 return message;
166 }
167 }
168
169 public void setMessages(CopyOnWriteArrayList<Message> msgs) {
170 this.messages = msgs;
171 }
172
173 public String getName() {
174 if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
175 return getMucOptions().getSubject();
176 } else if (getMode() == MODE_MULTI && bookmark != null
177 && bookmark.getName() != null) {
178 return bookmark.getName();
179 } else {
180 return this.getContact().getDisplayName();
181 }
182 }
183
184 public String getProfilePhotoString() {
185 return this.getContact().getProfilePhoto();
186 }
187
188 public String getAccountUuid() {
189 return this.accountUuid;
190 }
191
192 public Account getAccount() {
193 return this.account;
194 }
195
196 public Contact getContact() {
197 return this.account.getRoster().getContact(this.contactJid);
198 }
199
200 public void setAccount(Account account) {
201 this.account = account;
202 }
203
204 public String getContactJid() {
205 return this.contactJid;
206 }
207
208 public int getStatus() {
209 return this.status;
210 }
211
212 public long getCreated() {
213 return this.created;
214 }
215
216 public ContentValues getContentValues() {
217 ContentValues values = new ContentValues();
218 values.put(UUID, uuid);
219 values.put(NAME, name);
220 values.put(CONTACT, contactUuid);
221 values.put(ACCOUNT, accountUuid);
222 values.put(CONTACTJID, contactJid);
223 values.put(CREATED, created);
224 values.put(STATUS, status);
225 values.put(MODE, mode);
226 values.put(ATTRIBUTES, attributes.toString());
227 return values;
228 }
229
230 public static Conversation fromCursor(Cursor cursor) {
231 return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
232 cursor.getString(cursor.getColumnIndex(NAME)),
233 cursor.getString(cursor.getColumnIndex(CONTACT)),
234 cursor.getString(cursor.getColumnIndex(ACCOUNT)),
235 cursor.getString(cursor.getColumnIndex(CONTACTJID)),
236 cursor.getLong(cursor.getColumnIndex(CREATED)),
237 cursor.getInt(cursor.getColumnIndex(STATUS)),
238 cursor.getInt(cursor.getColumnIndex(MODE)),
239 cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
240 }
241
242 public void setStatus(int status) {
243 this.status = status;
244 }
245
246 public int getMode() {
247 return this.mode;
248 }
249
250 public void setMode(int mode) {
251 this.mode = mode;
252 }
253
254 public SessionImpl startOtrSession(XmppConnectionService service,
255 String presence, boolean sendStart) {
256 if (this.otrSession != null) {
257 return this.otrSession;
258 } else {
259 SessionID sessionId = new SessionID(this.getContactJid().split("/",
260 2)[0], presence, "xmpp");
261 this.otrSession = new SessionImpl(sessionId, getAccount()
262 .getOtrEngine(service));
263 try {
264 if (sendStart) {
265 this.otrSession.startSession();
266 this.otrSessionNeedsStarting = false;
267 return this.otrSession;
268 } else {
269 this.otrSessionNeedsStarting = true;
270 }
271 return this.otrSession;
272 } catch (OtrException e) {
273 return null;
274 }
275 }
276
277 }
278
279 public SessionImpl getOtrSession() {
280 return this.otrSession;
281 }
282
283 public void resetOtrSession() {
284 this.otrFingerprint = null;
285 this.otrSessionNeedsStarting = false;
286 this.otrSession = null;
287 }
288
289 public void startOtrIfNeeded() {
290 if (this.otrSession != null && this.otrSessionNeedsStarting) {
291 try {
292 this.otrSession.startSession();
293 } catch (OtrException e) {
294 this.resetOtrSession();
295 }
296 }
297 }
298
299 public void endOtrIfNeeded() {
300 if (this.otrSession != null) {
301 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
302 try {
303 this.otrSession.endSession();
304 this.resetOtrSession();
305 } catch (OtrException e) {
306 this.resetOtrSession();
307 }
308 } else {
309 this.resetOtrSession();
310 }
311 }
312 }
313
314 public boolean hasValidOtrSession() {
315 return this.otrSession != null;
316 }
317
318 public String getOtrFingerprint() {
319 if (this.otrFingerprint == null) {
320 try {
321 if (getOtrSession() == null) {
322 return "";
323 }
324 DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession()
325 .getRemotePublicKey();
326 StringBuilder builder = new StringBuilder(
327 new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
328 builder.insert(8, " ");
329 builder.insert(17, " ");
330 builder.insert(26, " ");
331 builder.insert(35, " ");
332 this.otrFingerprint = builder.toString();
333 } catch (OtrCryptoException e) {
334
335 }
336 }
337 return this.otrFingerprint;
338 }
339
340 public synchronized MucOptions getMucOptions() {
341 if (this.mucOptions == null) {
342 this.mucOptions = new MucOptions(this.getAccount());
343 }
344 this.mucOptions.setConversation(this);
345 return this.mucOptions;
346 }
347
348 public void resetMucOptions() {
349 this.mucOptions = null;
350 }
351
352 public void setContactJid(String jid) {
353 this.contactJid = jid;
354 }
355
356 public void setNextPresence(String presence) {
357 this.nextPresence = presence;
358 }
359
360 public String getNextPresence() {
361 return this.nextPresence;
362 }
363
364 public int getLatestEncryption() {
365 int latestEncryption = this.getLatestMessage().getEncryption();
366 if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
367 || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
368 return Message.ENCRYPTION_PGP;
369 } else {
370 return latestEncryption;
371 }
372 }
373
374 public int getNextEncryption(boolean force) {
375 int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
376 if (next == -1) {
377 int latest = this.getLatestEncryption();
378 if (latest == Message.ENCRYPTION_NONE) {
379 if (force && getMode() == MODE_SINGLE) {
380 return Message.ENCRYPTION_OTR;
381 } else if (getContact().getPresences().size() == 1) {
382 if (getContact().getOtrFingerprints().size() >= 1) {
383 return Message.ENCRYPTION_OTR;
384 } else {
385 return latest;
386 }
387 } else {
388 return latest;
389 }
390 } else {
391 return latest;
392 }
393 }
394 if (next == Message.ENCRYPTION_NONE && force
395 && getMode() == MODE_SINGLE) {
396 return Message.ENCRYPTION_OTR;
397 } else {
398 return next;
399 }
400 }
401
402 public void setNextEncryption(int encryption) {
403 this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
404 }
405
406 public String getNextMessage() {
407 if (this.nextMessage == null) {
408 return "";
409 } else {
410 return this.nextMessage;
411 }
412 }
413
414 public void setNextMessage(String message) {
415 this.nextMessage = message;
416 }
417
418 public void setSymmetricKey(byte[] key) {
419 this.symmetricKey = key;
420 }
421
422 public byte[] getSymmetricKey() {
423 return this.symmetricKey;
424 }
425
426 public void setBookmark(Bookmark bookmark) {
427 this.bookmark = bookmark;
428 this.bookmark.setConversation(this);
429 }
430
431 public void deregisterWithBookmark() {
432 if (this.bookmark != null) {
433 this.bookmark.setConversation(null);
434 }
435 }
436
437 public Bookmark getBookmark() {
438 return this.bookmark;
439 }
440
441 public Bitmap getImage(Context context, int size) {
442 if (mode == MODE_SINGLE) {
443 return getContact().getImage(size, context);
444 } else {
445 return UIHelper.getContactPicture(this, size, context, false);
446 }
447 }
448
449 public boolean hasDuplicateMessage(Message message) {
450 for (int i = this.getMessages().size() - 1; i >= 0; --i) {
451 if (this.messages.get(i).equals(message)) {
452 return true;
453 }
454 }
455 return false;
456 }
457
458 public void setMutedTill(long value) {
459 this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
460 }
461
462 public boolean isMuted() {
463 return SystemClock.elapsedRealtime() < this.getLongAttribute(
464 ATTRIBUTE_MUTED_TILL, 0);
465 }
466
467 public boolean setAttribute(String key, String value) {
468 try {
469 this.attributes.put(key, value);
470 return true;
471 } catch (JSONException e) {
472 return false;
473 }
474 }
475
476 public String getAttribute(String key) {
477 try {
478 return this.attributes.getString(key);
479 } catch (JSONException e) {
480 return null;
481 }
482 }
483
484 public int getIntAttribute(String key, int defaultValue) {
485 String value = this.getAttribute(key);
486 if (value == null) {
487 return defaultValue;
488 } else {
489 try {
490 return Integer.parseInt(value);
491 } catch (NumberFormatException e) {
492 return defaultValue;
493 }
494 }
495 }
496
497 public long getLongAttribute(String key, long defaultValue) {
498 String value = this.getAttribute(key);
499 if (value == null) {
500 return defaultValue;
501 } else {
502 try {
503 return Long.parseLong(value);
504 } catch (NumberFormatException e) {
505 return defaultValue;
506 }
507 }
508 }
509}