Conversation.java

  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(this.getContactJid().split("/",
251					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}