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