Conversation.java

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