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		for(int i = this.messages.size() - 1; i >= 0; --i) {
143			if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED && this.messages.get(i).markable) {
144				if (this.messages.get(i).isRead()) {
145					return null;
146				} else {
147					return this.messages.get(i).getRemoteMsgId();
148				}
149			}
150		}
151		return null;
152	}
153
154	public Message getLatestMessage() {
155		if ((this.messages == null) || (this.messages.size() == 0)) {
156			Message message = new Message(this, "", Message.ENCRYPTION_NONE);
157			message.setTime(getCreated());
158			return message;
159		} else {
160			Message message = this.messages.get(this.messages.size() - 1);
161			message.setConversation(this);
162			return message;
163		}
164	}
165
166	public void setMessages(CopyOnWriteArrayList<Message> msgs) {
167		this.messages = msgs;
168	}
169
170	public String getName() {
171		if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
172			return getMucOptions().getSubject();
173		} else if (getMode() == MODE_MULTI && bookmark != null
174				&& bookmark.getName() != null) {
175			return bookmark.getName();
176		} else {
177			return this.getContact().getDisplayName();
178		}
179	}
180
181	public String getProfilePhotoString() {
182		return this.getContact().getProfilePhoto();
183	}
184
185	public String getAccountUuid() {
186		return this.accountUuid;
187	}
188
189	public Account getAccount() {
190		return this.account;
191	}
192
193	public Contact getContact() {
194		return this.account.getRoster().getContact(this.contactJid);
195	}
196
197	public void setAccount(Account account) {
198		this.account = account;
199	}
200
201	public String getContactJid() {
202		return this.contactJid;
203	}
204
205	public int getStatus() {
206		return this.status;
207	}
208
209	public long getCreated() {
210		return this.created;
211	}
212
213	public ContentValues getContentValues() {
214		ContentValues values = new ContentValues();
215		values.put(UUID, uuid);
216		values.put(NAME, name);
217		values.put(CONTACT, contactUuid);
218		values.put(ACCOUNT, accountUuid);
219		values.put(CONTACTJID, contactJid);
220		values.put(CREATED, created);
221		values.put(STATUS, status);
222		values.put(MODE, mode);
223		values.put(ATTRIBUTES, attributes.toString());
224		return values;
225	}
226
227	public static Conversation fromCursor(Cursor cursor) {
228		return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
229				cursor.getString(cursor.getColumnIndex(NAME)),
230				cursor.getString(cursor.getColumnIndex(CONTACT)),
231				cursor.getString(cursor.getColumnIndex(ACCOUNT)),
232				cursor.getString(cursor.getColumnIndex(CONTACTJID)),
233				cursor.getLong(cursor.getColumnIndex(CREATED)),
234				cursor.getInt(cursor.getColumnIndex(STATUS)),
235				cursor.getInt(cursor.getColumnIndex(MODE)),
236				cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
237	}
238
239	public void setStatus(int status) {
240		this.status = status;
241	}
242
243	public int getMode() {
244		return this.mode;
245	}
246
247	public void setMode(int mode) {
248		this.mode = mode;
249	}
250
251	public SessionImpl startOtrSession(XmppConnectionService service,
252			String presence, boolean sendStart) {
253		if (this.otrSession != null) {
254			return this.otrSession;
255		} else {
256			SessionID sessionId = new SessionID(this.getContactJid().split("/",
257					2)[0], presence, "xmpp");
258			this.otrSession = new SessionImpl(sessionId, getAccount()
259					.getOtrEngine(service));
260			try {
261				if (sendStart) {
262					this.otrSession.startSession();
263					this.otrSessionNeedsStarting = false;
264					return this.otrSession;
265				} else {
266					this.otrSessionNeedsStarting = true;
267				}
268				return this.otrSession;
269			} catch (OtrException e) {
270				return null;
271			}
272		}
273
274	}
275
276	public SessionImpl getOtrSession() {
277		return this.otrSession;
278	}
279
280	public void resetOtrSession() {
281		this.otrFingerprint = null;
282		this.otrSessionNeedsStarting = false;
283		this.otrSession = null;
284	}
285
286	public void startOtrIfNeeded() {
287		if (this.otrSession != null && this.otrSessionNeedsStarting) {
288			try {
289				this.otrSession.startSession();
290			} catch (OtrException e) {
291				this.resetOtrSession();
292			}
293		}
294	}
295
296	public void endOtrIfNeeded() {
297		if (this.otrSession != null) {
298			if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
299				try {
300					this.otrSession.endSession();
301					this.resetOtrSession();
302				} catch (OtrException e) {
303					this.resetOtrSession();
304				}
305			} else {
306				this.resetOtrSession();
307			}
308		}
309	}
310
311	public boolean hasValidOtrSession() {
312		return this.otrSession != null;
313	}
314
315	public String getOtrFingerprint() {
316		if (this.otrFingerprint == null) {
317			try {
318				if (getOtrSession() == null) {
319					return "";
320				}
321				DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession()
322						.getRemotePublicKey();
323				StringBuilder builder = new StringBuilder(
324						new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
325				builder.insert(8, " ");
326				builder.insert(17, " ");
327				builder.insert(26, " ");
328				builder.insert(35, " ");
329				this.otrFingerprint = builder.toString();
330			} catch (OtrCryptoException e) {
331
332			}
333		}
334		return this.otrFingerprint;
335	}
336
337	public synchronized MucOptions getMucOptions() {
338		if (this.mucOptions == null) {
339			this.mucOptions = new MucOptions(this.getAccount());
340		}
341		this.mucOptions.setConversation(this);
342		return this.mucOptions;
343	}
344
345	public void resetMucOptions() {
346		this.mucOptions = null;
347	}
348
349	public void setContactJid(String jid) {
350		this.contactJid = jid;
351	}
352
353	public void setNextPresence(String presence) {
354		this.nextPresence = presence;
355	}
356
357	public String getNextPresence() {
358		return this.nextPresence;
359	}
360
361	public int getLatestEncryption() {
362		int latestEncryption = this.getLatestMessage().getEncryption();
363		if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
364				|| (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
365			return Message.ENCRYPTION_PGP;
366		} else {
367			return latestEncryption;
368		}
369	}
370
371	public int getNextEncryption(boolean force) {
372		int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
373		if (next == -1) {
374			int latest = this.getLatestEncryption();
375			if (latest == Message.ENCRYPTION_NONE) {
376				if (force && getMode() == MODE_SINGLE) {
377					return Message.ENCRYPTION_OTR;
378				} else if (getContact().getPresences().size() == 1) {
379					if (getContact().getOtrFingerprints().size() >= 1) {
380						return Message.ENCRYPTION_OTR;
381					} else {
382						return latest;
383					}
384				} else {
385					return latest;
386				}
387			} else {
388				return latest;
389			}
390		}
391		if (next == Message.ENCRYPTION_NONE && force
392				&& getMode() == MODE_SINGLE) {
393			return Message.ENCRYPTION_OTR;
394		} else {
395			return next;
396		}
397	}
398
399	public void setNextEncryption(int encryption) {
400		this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
401	}
402
403	public String getNextMessage() {
404		if (this.nextMessage == null) {
405			return "";
406		} else {
407			return this.nextMessage;
408		}
409	}
410
411	public void setNextMessage(String message) {
412		this.nextMessage = message;
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 value) {
456		this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
457	}
458
459	public boolean isMuted() {
460		return SystemClock.elapsedRealtime() < this.getLongAttribute(
461				ATTRIBUTE_MUTED_TILL, 0);
462	}
463
464	public boolean setAttribute(String key, String value) {
465		try {
466			this.attributes.put(key, value);
467			return true;
468		} catch (JSONException e) {
469			return false;
470		}
471	}
472
473	public String getAttribute(String key) {
474		try {
475			return this.attributes.getString(key);
476		} catch (JSONException e) {
477			return null;
478		}
479	}
480
481	public int getIntAttribute(String key, int defaultValue) {
482		String value = this.getAttribute(key);
483		if (value == null) {
484			return defaultValue;
485		} else {
486			try {
487				return Integer.parseInt(value);
488			} catch (NumberFormatException e) {
489				return defaultValue;
490			}
491		}
492	}
493
494	public long getLongAttribute(String key, long defaultValue) {
495		String value = this.getAttribute(key);
496		if (value == null) {
497			return defaultValue;
498		} else {
499			try {
500				return Long.parseLong(value);
501			} catch (NumberFormatException e) {
502				return defaultValue;
503			}
504		}
505	}
506}