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