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