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 void endOtrIfNeeded() {
301		if (this.otrSession != null) {
302			if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
303				try {
304					this.otrSession.endSession();
305					this.resetOtrSession();
306				} catch (OtrException e) {
307					this.resetOtrSession();
308				}
309			} else {
310				this.resetOtrSession();
311			}
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}