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