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