1package eu.siacs.conversations.entities;
2
3import java.security.interfaces.DSAPublicKey;
4import java.util.ArrayList;
5import java.util.List;
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 ArrayList<Message> messages = new ArrayList<Message>();
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 return messages;
108 }
109
110 public boolean isRead() {
111 if ((this.messages == null) || (this.messages.size() == 0))
112 return true;
113 return this.messages.get(this.messages.size() - 1).isRead();
114 }
115
116 public void markRead() {
117 if (this.messages == null) {
118 return;
119 }
120 for (int i = this.messages.size() - 1; i >= 0; --i) {
121 if (messages.get(i).isRead()) {
122 break;
123 }
124 this.messages.get(i).markRead();
125 }
126 }
127
128 public String getLatestMarkableMessageId() {
129 if (this.messages == null) {
130 return null;
131 }
132 for (int i = this.messages.size() - 1; i >= 0; --i) {
133 if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
134 && this.messages.get(i).markable) {
135 if (this.messages.get(i).isRead()) {
136 return null;
137 } else {
138 return this.messages.get(i).getRemoteMsgId();
139 }
140 }
141 }
142 return null;
143 }
144
145 public Message getLatestMessage() {
146 if ((this.messages == null) || (this.messages.size() == 0)) {
147 Message message = new Message(this, "", Message.ENCRYPTION_NONE);
148 message.setTime(getCreated());
149 return message;
150 } else {
151 Message message = this.messages.get(this.messages.size() - 1);
152 message.setConversation(this);
153 return message;
154 }
155 }
156
157 public void setMessages(ArrayList<Message> msgs) {
158 this.messages = msgs;
159 }
160
161 public String getName() {
162 if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
163 return getMucOptions().getSubject();
164 } else if (getMode() == MODE_MULTI && bookmark != null
165 && bookmark.getName() != null) {
166 return bookmark.getName();
167 } else {
168 return this.getContact().getDisplayName();
169 }
170 }
171
172 public String getProfilePhotoString() {
173 return this.getContact().getProfilePhoto();
174 }
175
176 public String getAccountUuid() {
177 return this.accountUuid;
178 }
179
180 public Account getAccount() {
181 return this.account;
182 }
183
184 public Contact getContact() {
185 return this.account.getRoster().getContact(this.contactJid);
186 }
187
188 public void setAccount(Account account) {
189 this.account = account;
190 }
191
192 public String getContactJid() {
193 return this.contactJid;
194 }
195
196 public int getStatus() {
197 return this.status;
198 }
199
200 public long getCreated() {
201 return this.created;
202 }
203
204 public ContentValues getContentValues() {
205 ContentValues values = new ContentValues();
206 values.put(UUID, uuid);
207 values.put(NAME, name);
208 values.put(CONTACT, contactUuid);
209 values.put(ACCOUNT, accountUuid);
210 values.put(CONTACTJID, contactJid);
211 values.put(CREATED, created);
212 values.put(STATUS, status);
213 values.put(MODE, mode);
214 values.put(ATTRIBUTES, attributes.toString());
215 return values;
216 }
217
218 public static Conversation fromCursor(Cursor cursor) {
219 return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
220 cursor.getString(cursor.getColumnIndex(NAME)),
221 cursor.getString(cursor.getColumnIndex(CONTACT)),
222 cursor.getString(cursor.getColumnIndex(ACCOUNT)),
223 cursor.getString(cursor.getColumnIndex(CONTACTJID)),
224 cursor.getLong(cursor.getColumnIndex(CREATED)),
225 cursor.getInt(cursor.getColumnIndex(STATUS)),
226 cursor.getInt(cursor.getColumnIndex(MODE)),
227 cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
228 }
229
230 public void setStatus(int status) {
231 this.status = status;
232 }
233
234 public int getMode() {
235 return this.mode;
236 }
237
238 public void setMode(int mode) {
239 this.mode = mode;
240 }
241
242 public SessionImpl startOtrSession(XmppConnectionService service,
243 String presence, boolean sendStart) {
244 if (this.otrSession != null) {
245 return this.otrSession;
246 } else {
247 SessionID sessionId = new SessionID(this.getContactJid().split("/",
248 2)[0], presence, "xmpp");
249 this.otrSession = new SessionImpl(sessionId, getAccount()
250 .getOtrEngine(service));
251 try {
252 if (sendStart) {
253 this.otrSession.startSession();
254 return this.otrSession;
255 }
256 return this.otrSession;
257 } catch (OtrException e) {
258 return null;
259 }
260 }
261
262 }
263
264 public SessionImpl getOtrSession() {
265 return this.otrSession;
266 }
267
268 public void resetOtrSession() {
269 this.otrFingerprint = null;
270 this.otrSession = null;
271 }
272
273 public void startOtrIfNeeded() {
274 if (this.otrSession != null
275 && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
276 try {
277 this.otrSession.startSession();
278 } catch (OtrException e) {
279 this.resetOtrSession();
280 }
281 }
282 }
283
284 public boolean endOtrIfNeeded() {
285 if (this.otrSession != null) {
286 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
287 try {
288 this.otrSession.endSession();
289 this.resetOtrSession();
290 return true;
291 } catch (OtrException e) {
292 this.resetOtrSession();
293 return false;
294 }
295 } else {
296 this.resetOtrSession();
297 return false;
298 }
299 } else {
300 return false;
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 setSymmetricKey(byte[] key) {
409 this.symmetricKey = key;
410 }
411
412 public byte[] getSymmetricKey() {
413 return this.symmetricKey;
414 }
415
416 public void setBookmark(Bookmark bookmark) {
417 this.bookmark = bookmark;
418 this.bookmark.setConversation(this);
419 }
420
421 public void deregisterWithBookmark() {
422 if (this.bookmark != null) {
423 this.bookmark.setConversation(null);
424 }
425 }
426
427 public Bookmark getBookmark() {
428 return this.bookmark;
429 }
430
431 public Bitmap getImage(Context context, int size) {
432 if (mode == MODE_SINGLE) {
433 return getContact().getImage(size, context);
434 } else {
435 return UIHelper.getContactPicture(this, size, context, false);
436 }
437 }
438
439 public boolean hasDuplicateMessage(Message message) {
440 for (int i = this.getMessages().size() - 1; i >= 0; --i) {
441 if (this.messages.get(i).equals(message)) {
442 return true;
443 }
444 }
445 return false;
446 }
447
448 public void setMutedTill(long value) {
449 this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
450 }
451
452 public boolean isMuted() {
453 return SystemClock.elapsedRealtime() < this.getLongAttribute(
454 ATTRIBUTE_MUTED_TILL, 0);
455 }
456
457 public boolean setAttribute(String key, String value) {
458 try {
459 this.attributes.put(key, value);
460 return true;
461 } catch (JSONException e) {
462 return false;
463 }
464 }
465
466 public String getAttribute(String key) {
467 try {
468 return this.attributes.getString(key);
469 } catch (JSONException e) {
470 return null;
471 }
472 }
473
474 public int getIntAttribute(String key, int defaultValue) {
475 String value = this.getAttribute(key);
476 if (value == null) {
477 return defaultValue;
478 } else {
479 try {
480 return Integer.parseInt(value);
481 } catch (NumberFormatException e) {
482 return defaultValue;
483 }
484 }
485 }
486
487 public long getLongAttribute(String key, long defaultValue) {
488 String value = this.getAttribute(key);
489 if (value == null) {
490 return defaultValue;
491 } else {
492 try {
493 return Long.parseLong(value);
494 } catch (NumberFormatException e) {
495 return defaultValue;
496 }
497 }
498 }
499
500 public void add(Message message) {
501 message.setConversation(this);
502 synchronized (this.messages) {
503 this.messages.add(message);
504 }
505 }
506
507 public void addAll(int index, List<Message> messages) {
508 synchronized (this.messages) {
509 this.messages.addAll(index, messages);
510 }
511 }
512}