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
47 private String name;
48 private String contactUuid;
49 private String accountUuid;
50 private String contactJid;
51 private int status;
52 private long created;
53 private int mode;
54
55 private JSONObject attributes = new JSONObject();
56
57 private long mutedTill = 0;
58
59 private String nextPresence;
60
61 private transient CopyOnWriteArrayList<Message> messages = null;
62 private transient Account account = null;
63
64 private transient SessionImpl otrSession;
65
66 private transient String otrFingerprint = null;
67
68 private String nextMessage;
69
70 private transient MucOptions mucOptions = null;
71
72 private transient String latestMarkableMessageId;
73
74 private byte[] symmetricKey;
75
76 private boolean otrSessionNeedsStarting = false;
77
78 private Bookmark bookmark;
79
80 public Conversation(String name, Account account, String contactJid,
81 int mode) {
82 this(java.util.UUID.randomUUID().toString(), name, null, account
83 .getUuid(), contactJid, System.currentTimeMillis(),
84 STATUS_AVAILABLE, mode,"");
85 this.account = account;
86 }
87
88 public Conversation(String uuid, String name, String contactUuid,
89 String accountUuid, String contactJid, long created, int status,
90 int mode, String attributes) {
91 this.uuid = uuid;
92 this.name = name;
93 this.contactUuid = contactUuid;
94 this.accountUuid = accountUuid;
95 this.contactJid = contactJid;
96 this.created = created;
97 this.status = status;
98 this.mode = mode;
99 try {
100 if (attributes==null) {
101 attributes = new String();
102 }
103 this.attributes = new JSONObject(attributes);
104 } catch (JSONException e) {
105 this.attributes = new JSONObject();
106 }
107 }
108
109 public List<Message> getMessages() {
110 if (messages == null) {
111 this.messages = new CopyOnWriteArrayList<Message>(); // prevent null
112 // pointer
113 }
114
115 // populate with Conversation (this)
116
117 for (Message msg : messages) {
118 msg.setConversation(this);
119 }
120
121 return messages;
122 }
123
124 public boolean isRead() {
125 if ((this.messages == null) || (this.messages.size() == 0))
126 return true;
127 return this.messages.get(this.messages.size() - 1).isRead();
128 }
129
130 public void markRead() {
131 if (this.messages == null) {
132 return;
133 }
134 for (int i = this.messages.size() - 1; i >= 0; --i) {
135 if (messages.get(i).isRead()) {
136 break;
137 }
138 this.messages.get(i).markRead();
139 }
140 }
141
142 public String popLatestMarkableMessageId() {
143 String id = this.latestMarkableMessageId;
144 this.latestMarkableMessageId = null;
145 return id;
146 }
147
148 public Message getLatestMessage() {
149 if ((this.messages == null) || (this.messages.size() == 0)) {
150 Message message = new Message(this, "", Message.ENCRYPTION_NONE);
151 message.setTime(getCreated());
152 return message;
153 } else {
154 Message message = this.messages.get(this.messages.size() - 1);
155 message.setConversation(this);
156 return message;
157 }
158 }
159
160 public void setMessages(CopyOnWriteArrayList<Message> msgs) {
161 this.messages = msgs;
162 }
163
164 public String getName() {
165 if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
166 return getMucOptions().getSubject();
167 } else if (getMode() == MODE_MULTI && bookmark != null
168 && bookmark.getName() != null) {
169 return bookmark.getName();
170 } else {
171 return this.getContact().getDisplayName();
172 }
173 }
174
175 public String getProfilePhotoString() {
176 return this.getContact().getProfilePhoto();
177 }
178
179 public String getAccountUuid() {
180 return this.accountUuid;
181 }
182
183 public Account getAccount() {
184 return this.account;
185 }
186
187 public Contact getContact() {
188 return this.account.getRoster().getContact(this.contactJid);
189 }
190
191 public void setAccount(Account account) {
192 this.account = account;
193 }
194
195 public String getContactJid() {
196 return this.contactJid;
197 }
198
199 public int getStatus() {
200 return this.status;
201 }
202
203 public long getCreated() {
204 return this.created;
205 }
206
207 public ContentValues getContentValues() {
208 ContentValues values = new ContentValues();
209 values.put(UUID, uuid);
210 values.put(NAME, name);
211 values.put(CONTACT, contactUuid);
212 values.put(ACCOUNT, accountUuid);
213 values.put(CONTACTJID, contactJid);
214 values.put(CREATED, created);
215 values.put(STATUS, status);
216 values.put(MODE, mode);
217 values.put(ATTRIBUTES,attributes.toString());
218 return values;
219 }
220
221 public static Conversation fromCursor(Cursor cursor) {
222 return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
223 cursor.getString(cursor.getColumnIndex(NAME)),
224 cursor.getString(cursor.getColumnIndex(CONTACT)),
225 cursor.getString(cursor.getColumnIndex(ACCOUNT)),
226 cursor.getString(cursor.getColumnIndex(CONTACTJID)),
227 cursor.getLong(cursor.getColumnIndex(CREATED)),
228 cursor.getInt(cursor.getColumnIndex(STATUS)),
229 cursor.getInt(cursor.getColumnIndex(MODE)),
230 cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
231 }
232
233 public void setStatus(int status) {
234 this.status = status;
235 }
236
237 public int getMode() {
238 return this.mode;
239 }
240
241 public void setMode(int mode) {
242 this.mode = mode;
243 }
244
245 public SessionImpl startOtrSession(XmppConnectionService service,
246 String presence, boolean sendStart) {
247 if (this.otrSession != null) {
248 return this.otrSession;
249 } else {
250 SessionID sessionId = new SessionID(
251 this.getContactJid().split("/",2)[0], presence, "xmpp");
252 this.otrSession = new SessionImpl(sessionId, getAccount()
253 .getOtrEngine(service));
254 try {
255 if (sendStart) {
256 this.otrSession.startSession();
257 this.otrSessionNeedsStarting = false;
258 return this.otrSession;
259 } else {
260 this.otrSessionNeedsStarting = true;
261 }
262 return this.otrSession;
263 } catch (OtrException e) {
264 return null;
265 }
266 }
267
268 }
269
270 public SessionImpl getOtrSession() {
271 return this.otrSession;
272 }
273
274 public void resetOtrSession() {
275 this.otrFingerprint = null;
276 this.otrSessionNeedsStarting = false;
277 this.otrSession = null;
278 }
279
280 public void startOtrIfNeeded() {
281 if (this.otrSession != null && this.otrSessionNeedsStarting) {
282 try {
283 this.otrSession.startSession();
284 } catch (OtrException e) {
285 this.resetOtrSession();
286 }
287 }
288 }
289
290 public void endOtrIfNeeded() {
291 if (this.otrSession != null) {
292 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
293 try {
294 this.otrSession.endSession();
295 this.resetOtrSession();
296 } catch (OtrException e) {
297 this.resetOtrSession();
298 }
299 } else {
300 this.resetOtrSession();
301 }
302 }
303 }
304
305 public boolean hasValidOtrSession() {
306 return this.otrSession != null;
307 }
308
309 public String getOtrFingerprint() {
310 if (this.otrFingerprint == null) {
311 try {
312 if (getOtrSession() == null) {
313 return "";
314 }
315 DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession()
316 .getRemotePublicKey();
317 StringBuilder builder = new StringBuilder(
318 new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
319 builder.insert(8, " ");
320 builder.insert(17, " ");
321 builder.insert(26, " ");
322 builder.insert(35, " ");
323 this.otrFingerprint = builder.toString();
324 } catch (OtrCryptoException e) {
325
326 }
327 }
328 return this.otrFingerprint;
329 }
330
331 public synchronized MucOptions getMucOptions() {
332 if (this.mucOptions == null) {
333 this.mucOptions = new MucOptions(this.getAccount());
334 }
335 this.mucOptions.setConversation(this);
336 return this.mucOptions;
337 }
338
339 public void resetMucOptions() {
340 this.mucOptions = null;
341 }
342
343 public void setContactJid(String jid) {
344 this.contactJid = jid;
345 }
346
347 public void setNextPresence(String presence) {
348 this.nextPresence = presence;
349 }
350
351 public String getNextPresence() {
352 return this.nextPresence;
353 }
354
355 public int getLatestEncryption() {
356 int latestEncryption = this.getLatestMessage().getEncryption();
357 if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
358 || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
359 return Message.ENCRYPTION_PGP;
360 } else {
361 return latestEncryption;
362 }
363 }
364
365 public int getNextEncryption(boolean force) {
366 int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
367 if (next == -1) {
368 int latest = this.getLatestEncryption();
369 if (latest == Message.ENCRYPTION_NONE) {
370 if (force && getMode() == MODE_SINGLE) {
371 return Message.ENCRYPTION_OTR;
372 } else if (getContact().getPresences().size() == 1) {
373 if (getContact().getOtrFingerprints().size() >= 1) {
374 return Message.ENCRYPTION_OTR;
375 } else {
376 return latest;
377 }
378 } else {
379 return latest;
380 }
381 } else {
382 return latest;
383 }
384 }
385 if (next == Message.ENCRYPTION_NONE && force
386 && getMode() == MODE_SINGLE) {
387 return Message.ENCRYPTION_OTR;
388 } else {
389 return next;
390 }
391 }
392
393 public void setNextEncryption(int encryption) {
394 this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
395 }
396
397 public String getNextMessage() {
398 if (this.nextMessage == null) {
399 return "";
400 } else {
401 return this.nextMessage;
402 }
403 }
404
405 public void setNextMessage(String message) {
406 this.nextMessage = message;
407 }
408
409 public void setLatestMarkableMessageId(String id) {
410 if (id != null) {
411 this.latestMarkableMessageId = id;
412 }
413 }
414
415 public void setSymmetricKey(byte[] key) {
416 this.symmetricKey = key;
417 }
418
419 public byte[] getSymmetricKey() {
420 return this.symmetricKey;
421 }
422
423 public void setBookmark(Bookmark bookmark) {
424 this.bookmark = bookmark;
425 this.bookmark.setConversation(this);
426 }
427
428 public void deregisterWithBookmark() {
429 if (this.bookmark != null) {
430 this.bookmark.setConversation(null);
431 }
432 }
433
434 public Bookmark getBookmark() {
435 return this.bookmark;
436 }
437
438 public Bitmap getImage(Context context, int size) {
439 if (mode == MODE_SINGLE) {
440 return getContact().getImage(size, context);
441 } else {
442 return UIHelper.getContactPicture(this, size, context, false);
443 }
444 }
445
446 public boolean hasDuplicateMessage(Message message) {
447 for (int i = this.getMessages().size() - 1; i >= 0; --i) {
448 if (this.messages.get(i).equals(message)) {
449 return true;
450 }
451 }
452 return false;
453 }
454
455 public void setMutedTill(long mutedTill) {
456 this.mutedTill = mutedTill;
457 }
458
459 public boolean isMuted() {
460 return SystemClock.elapsedRealtime() < this.mutedTill;
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}