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