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 boolean endOtrIfNeeded() {
301 if (this.otrSession != null) {
302 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
303 try {
304 this.otrSession.endSession();
305 this.resetOtrSession();
306 return true;
307 } catch (OtrException e) {
308 this.resetOtrSession();
309 return false;
310 }
311 } else {
312 this.resetOtrSession();
313 return false;
314 }
315 } else {
316 return false;
317 }
318 }
319
320 public boolean hasValidOtrSession() {
321 return this.otrSession != null;
322 }
323
324 public String getOtrFingerprint() {
325 if (this.otrFingerprint == null) {
326 try {
327 if (getOtrSession() == null) {
328 return "";
329 }
330 DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession()
331 .getRemotePublicKey();
332 StringBuilder builder = new StringBuilder(
333 new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
334 builder.insert(8, " ");
335 builder.insert(17, " ");
336 builder.insert(26, " ");
337 builder.insert(35, " ");
338 this.otrFingerprint = builder.toString();
339 } catch (OtrCryptoException e) {
340
341 }
342 }
343 return this.otrFingerprint;
344 }
345
346 public synchronized MucOptions getMucOptions() {
347 if (this.mucOptions == null) {
348 this.mucOptions = new MucOptions(this.getAccount());
349 }
350 this.mucOptions.setConversation(this);
351 return this.mucOptions;
352 }
353
354 public void resetMucOptions() {
355 this.mucOptions = null;
356 }
357
358 public void setContactJid(String jid) {
359 this.contactJid = jid;
360 }
361
362 public void setNextPresence(String presence) {
363 this.nextPresence = presence;
364 }
365
366 public String getNextPresence() {
367 return this.nextPresence;
368 }
369
370 public int getLatestEncryption() {
371 int latestEncryption = this.getLatestMessage().getEncryption();
372 if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
373 || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
374 return Message.ENCRYPTION_PGP;
375 } else {
376 return latestEncryption;
377 }
378 }
379
380 public int getNextEncryption(boolean force) {
381 int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
382 if (next == -1) {
383 int latest = this.getLatestEncryption();
384 if (latest == Message.ENCRYPTION_NONE) {
385 if (force && getMode() == MODE_SINGLE) {
386 return Message.ENCRYPTION_OTR;
387 } else if (getContact().getPresences().size() == 1) {
388 if (getContact().getOtrFingerprints().size() >= 1) {
389 return Message.ENCRYPTION_OTR;
390 } else {
391 return latest;
392 }
393 } else {
394 return latest;
395 }
396 } else {
397 return latest;
398 }
399 }
400 if (next == Message.ENCRYPTION_NONE && force
401 && getMode() == MODE_SINGLE) {
402 return Message.ENCRYPTION_OTR;
403 } else {
404 return next;
405 }
406 }
407
408 public void setNextEncryption(int encryption) {
409 this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
410 }
411
412 public String getNextMessage() {
413 if (this.nextMessage == null) {
414 return "";
415 } else {
416 return this.nextMessage;
417 }
418 }
419
420 public void setNextMessage(String message) {
421 this.nextMessage = message;
422 }
423
424 public void setSymmetricKey(byte[] key) {
425 this.symmetricKey = key;
426 }
427
428 public byte[] getSymmetricKey() {
429 return this.symmetricKey;
430 }
431
432 public void setBookmark(Bookmark bookmark) {
433 this.bookmark = bookmark;
434 this.bookmark.setConversation(this);
435 }
436
437 public void deregisterWithBookmark() {
438 if (this.bookmark != null) {
439 this.bookmark.setConversation(null);
440 }
441 }
442
443 public Bookmark getBookmark() {
444 return this.bookmark;
445 }
446
447 public Bitmap getImage(Context context, int size) {
448 if (mode == MODE_SINGLE) {
449 return getContact().getImage(size, context);
450 } else {
451 return UIHelper.getContactPicture(this, size, context, false);
452 }
453 }
454
455 public boolean hasDuplicateMessage(Message message) {
456 for (int i = this.getMessages().size() - 1; i >= 0; --i) {
457 if (this.messages.get(i).equals(message)) {
458 return true;
459 }
460 }
461 return false;
462 }
463
464 public void setMutedTill(long value) {
465 this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
466 }
467
468 public boolean isMuted() {
469 return SystemClock.elapsedRealtime() < this.getLongAttribute(
470 ATTRIBUTE_MUTED_TILL, 0);
471 }
472
473 public boolean setAttribute(String key, String value) {
474 try {
475 this.attributes.put(key, value);
476 return true;
477 } catch (JSONException e) {
478 return false;
479 }
480 }
481
482 public String getAttribute(String key) {
483 try {
484 return this.attributes.getString(key);
485 } catch (JSONException e) {
486 return null;
487 }
488 }
489
490 public int getIntAttribute(String key, int defaultValue) {
491 String value = this.getAttribute(key);
492 if (value == null) {
493 return defaultValue;
494 } else {
495 try {
496 return Integer.parseInt(value);
497 } catch (NumberFormatException e) {
498 return defaultValue;
499 }
500 }
501 }
502
503 public long getLongAttribute(String key, long defaultValue) {
504 String value = this.getAttribute(key);
505 if (value == null) {
506 return defaultValue;
507 } else {
508 try {
509 return Long.parseLong(value);
510 } catch (NumberFormatException e) {
511 return defaultValue;
512 }
513 }
514 }
515}