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