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 Bookmark bookmark;
76
77 public Conversation(String name, Account account, String contactJid,
78 int mode) {
79 this(java.util.UUID.randomUUID().toString(), name, null, account
80 .getUuid(), contactJid, System.currentTimeMillis(),
81 STATUS_AVAILABLE, mode, "");
82 this.account = account;
83 }
84
85 public Conversation(String uuid, String name, String contactUuid,
86 String accountUuid, String contactJid, long created, int status,
87 int mode, String attributes) {
88 this.uuid = uuid;
89 this.name = name;
90 this.contactUuid = contactUuid;
91 this.accountUuid = accountUuid;
92 this.contactJid = contactJid;
93 this.created = created;
94 this.status = status;
95 this.mode = mode;
96 try {
97 if (attributes == null) {
98 attributes = new String();
99 }
100 this.attributes = new JSONObject(attributes);
101 } catch (JSONException e) {
102 this.attributes = new JSONObject();
103 }
104 }
105
106 public List<Message> getMessages() {
107 if (messages == null) {
108 this.messages = new CopyOnWriteArrayList<Message>(); // prevent null
109 // pointer
110 }
111
112 // populate with Conversation (this)
113
114 for (Message msg : messages) {
115 msg.setConversation(this);
116 }
117
118 return messages;
119 }
120
121 public boolean isRead() {
122 if ((this.messages == null) || (this.messages.size() == 0))
123 return true;
124 return this.messages.get(this.messages.size() - 1).isRead();
125 }
126
127 public void markRead() {
128 if (this.messages == null) {
129 return;
130 }
131 for (int i = this.messages.size() - 1; i >= 0; --i) {
132 if (messages.get(i).isRead()) {
133 break;
134 }
135 this.messages.get(i).markRead();
136 }
137 }
138
139 public String getLatestMarkableMessageId() {
140 if (this.messages == null) {
141 return null;
142 }
143 for (int i = this.messages.size() - 1; i >= 0; --i) {
144 if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
145 && this.messages.get(i).markable) {
146 if (this.messages.get(i).isRead()) {
147 return null;
148 } else {
149 return this.messages.get(i).getRemoteMsgId();
150 }
151 }
152 }
153 return null;
154 }
155
156 public Message getLatestMessage() {
157 if ((this.messages == null) || (this.messages.size() == 0)) {
158 Message message = new Message(this, "", Message.ENCRYPTION_NONE);
159 message.setTime(getCreated());
160 return message;
161 } else {
162 Message message = this.messages.get(this.messages.size() - 1);
163 message.setConversation(this);
164 return message;
165 }
166 }
167
168 public void setMessages(CopyOnWriteArrayList<Message> msgs) {
169 this.messages = msgs;
170 }
171
172 public String getName() {
173 if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
174 return getMucOptions().getSubject();
175 } else if (getMode() == MODE_MULTI && bookmark != null
176 && bookmark.getName() != null) {
177 return bookmark.getName();
178 } else {
179 return this.getContact().getDisplayName();
180 }
181 }
182
183 public String getProfilePhotoString() {
184 return this.getContact().getProfilePhoto();
185 }
186
187 public String getAccountUuid() {
188 return this.accountUuid;
189 }
190
191 public Account getAccount() {
192 return this.account;
193 }
194
195 public Contact getContact() {
196 return this.account.getRoster().getContact(this.contactJid);
197 }
198
199 public void setAccount(Account account) {
200 this.account = account;
201 }
202
203 public String getContactJid() {
204 return this.contactJid;
205 }
206
207 public int getStatus() {
208 return this.status;
209 }
210
211 public long getCreated() {
212 return this.created;
213 }
214
215 public ContentValues getContentValues() {
216 ContentValues values = new ContentValues();
217 values.put(UUID, uuid);
218 values.put(NAME, name);
219 values.put(CONTACT, contactUuid);
220 values.put(ACCOUNT, accountUuid);
221 values.put(CONTACTJID, contactJid);
222 values.put(CREATED, created);
223 values.put(STATUS, status);
224 values.put(MODE, mode);
225 values.put(ATTRIBUTES, attributes.toString());
226 return values;
227 }
228
229 public static Conversation fromCursor(Cursor cursor) {
230 return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
231 cursor.getString(cursor.getColumnIndex(NAME)),
232 cursor.getString(cursor.getColumnIndex(CONTACT)),
233 cursor.getString(cursor.getColumnIndex(ACCOUNT)),
234 cursor.getString(cursor.getColumnIndex(CONTACTJID)),
235 cursor.getLong(cursor.getColumnIndex(CREATED)),
236 cursor.getInt(cursor.getColumnIndex(STATUS)),
237 cursor.getInt(cursor.getColumnIndex(MODE)),
238 cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
239 }
240
241 public void setStatus(int status) {
242 this.status = status;
243 }
244
245 public int getMode() {
246 return this.mode;
247 }
248
249 public void setMode(int mode) {
250 this.mode = mode;
251 }
252
253 public SessionImpl startOtrSession(XmppConnectionService service,
254 String presence, boolean sendStart) {
255 if (this.otrSession != null) {
256 return this.otrSession;
257 } else {
258 SessionID sessionId = new SessionID(this.getContactJid().split("/",
259 2)[0], presence, "xmpp");
260 this.otrSession = new SessionImpl(sessionId, getAccount()
261 .getOtrEngine(service));
262 try {
263 if (sendStart) {
264 this.otrSession.startSession();
265 return this.otrSession;
266 }
267 return this.otrSession;
268 } catch (OtrException e) {
269 return null;
270 }
271 }
272
273 }
274
275 public SessionImpl getOtrSession() {
276 return this.otrSession;
277 }
278
279 public void resetOtrSession() {
280 this.otrFingerprint = null;
281 this.otrSession = null;
282 }
283
284 public void startOtrIfNeeded() {
285 if (this.otrSession != null
286 && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
287 try {
288 this.otrSession.startSession();
289 } catch (OtrException e) {
290 this.resetOtrSession();
291 }
292 }
293 }
294
295 public boolean endOtrIfNeeded() {
296 if (this.otrSession != null) {
297 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
298 try {
299 this.otrSession.endSession();
300 this.resetOtrSession();
301 return true;
302 } catch (OtrException e) {
303 this.resetOtrSession();
304 return false;
305 }
306 } else {
307 this.resetOtrSession();
308 return false;
309 }
310 } else {
311 return false;
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}