Conversation.java

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