Conversation.java

  1package eu.siacs.conversations.entities;
  2
  3import java.security.interfaces.DSAPublicKey;
  4import java.util.ArrayList;
  5import java.util.List;
  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	protected ArrayList<Message> messages = new ArrayList<Message>();
 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(String name, Account account, String contactJid,
 78			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(String uuid, String name, String contactUuid,
 86			String accountUuid, String contactJid, long created, int status,
 87			int mode, 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			if (attributes == null) {
 98				attributes = new String();
 99			}
100			this.attributes = new JSONObject(attributes);
101		} catch (JSONException e) {
102			this.attributes = new JSONObject();
103		}
104	}
105
106	public List<Message> getMessages() {
107		return messages;
108	}
109
110	public boolean isRead() {
111		if ((this.messages == null) || (this.messages.size() == 0))
112			return true;
113		return this.messages.get(this.messages.size() - 1).isRead();
114	}
115
116	public void markRead() {
117		if (this.messages == null) {
118			return;
119		}
120		for (int i = this.messages.size() - 1; i >= 0; --i) {
121			if (messages.get(i).isRead()) {
122				break;
123			}
124			this.messages.get(i).markRead();
125		}
126	}
127
128	public String getLatestMarkableMessageId() {
129		if (this.messages == null) {
130			return null;
131		}
132		for (int i = this.messages.size() - 1; i >= 0; --i) {
133			if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
134					&& this.messages.get(i).markable) {
135				if (this.messages.get(i).isRead()) {
136					return null;
137				} else {
138					return this.messages.get(i).getRemoteMsgId();
139				}
140			}
141		}
142		return null;
143	}
144
145	public Message getLatestMessage() {
146		if ((this.messages == null) || (this.messages.size() == 0)) {
147			Message message = new Message(this, "", Message.ENCRYPTION_NONE);
148			message.setTime(getCreated());
149			return message;
150		} else {
151			Message message = this.messages.get(this.messages.size() - 1);
152			message.setConversation(this);
153			return message;
154		}
155	}
156
157	public void setMessages(ArrayList<Message> msgs) {
158		this.messages = msgs;
159	}
160
161	public String getName() {
162		if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
163			return getMucOptions().getSubject();
164		} else if (getMode() == MODE_MULTI && bookmark != null
165				&& bookmark.getName() != null) {
166			return bookmark.getName();
167		} else {
168			return this.getContact().getDisplayName();
169		}
170	}
171
172	public String getProfilePhotoString() {
173		return this.getContact().getProfilePhoto();
174	}
175
176	public String getAccountUuid() {
177		return this.accountUuid;
178	}
179
180	public Account getAccount() {
181		return this.account;
182	}
183
184	public Contact getContact() {
185		return this.account.getRoster().getContact(this.contactJid);
186	}
187
188	public void setAccount(Account account) {
189		this.account = account;
190	}
191
192	public String getContactJid() {
193		return this.contactJid;
194	}
195
196	public int getStatus() {
197		return this.status;
198	}
199
200	public long getCreated() {
201		return this.created;
202	}
203
204	public ContentValues getContentValues() {
205		ContentValues values = new ContentValues();
206		values.put(UUID, uuid);
207		values.put(NAME, name);
208		values.put(CONTACT, contactUuid);
209		values.put(ACCOUNT, accountUuid);
210		values.put(CONTACTJID, contactJid);
211		values.put(CREATED, created);
212		values.put(STATUS, status);
213		values.put(MODE, mode);
214		values.put(ATTRIBUTES, attributes.toString());
215		return values;
216	}
217
218	public static Conversation fromCursor(Cursor cursor) {
219		return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
220				cursor.getString(cursor.getColumnIndex(NAME)),
221				cursor.getString(cursor.getColumnIndex(CONTACT)),
222				cursor.getString(cursor.getColumnIndex(ACCOUNT)),
223				cursor.getString(cursor.getColumnIndex(CONTACTJID)),
224				cursor.getLong(cursor.getColumnIndex(CREATED)),
225				cursor.getInt(cursor.getColumnIndex(STATUS)),
226				cursor.getInt(cursor.getColumnIndex(MODE)),
227				cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
228	}
229
230	public void setStatus(int status) {
231		this.status = status;
232	}
233
234	public int getMode() {
235		return this.mode;
236	}
237
238	public void setMode(int mode) {
239		this.mode = mode;
240	}
241
242	public SessionImpl startOtrSession(XmppConnectionService service,
243			String presence, boolean sendStart) {
244		if (this.otrSession != null) {
245			return this.otrSession;
246		} else {
247			SessionID sessionId = new SessionID(this.getContactJid().split("/",
248					2)[0], presence, "xmpp");
249			this.otrSession = new SessionImpl(sessionId, getAccount()
250					.getOtrEngine(service));
251			try {
252				if (sendStart) {
253					this.otrSession.startSession();
254					return this.otrSession;
255				}
256				return this.otrSession;
257			} catch (OtrException e) {
258				return null;
259			}
260		}
261
262	}
263
264	public SessionImpl getOtrSession() {
265		return this.otrSession;
266	}
267
268	public void resetOtrSession() {
269		this.otrFingerprint = null;
270		this.otrSession = null;
271	}
272
273	public void startOtrIfNeeded() {
274		if (this.otrSession != null
275				&& this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
276			try {
277				this.otrSession.startSession();
278			} catch (OtrException e) {
279				this.resetOtrSession();
280			}
281		}
282	}
283
284	public boolean endOtrIfNeeded() {
285		if (this.otrSession != null) {
286			if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
287				try {
288					this.otrSession.endSession();
289					this.resetOtrSession();
290					return true;
291				} catch (OtrException e) {
292					this.resetOtrSession();
293					return false;
294				}
295			} else {
296				this.resetOtrSession();
297				return false;
298			}
299		} else {
300			return false;
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 setSymmetricKey(byte[] key) {
409		this.symmetricKey = key;
410	}
411
412	public byte[] getSymmetricKey() {
413		return this.symmetricKey;
414	}
415
416	public void setBookmark(Bookmark bookmark) {
417		this.bookmark = bookmark;
418		this.bookmark.setConversation(this);
419	}
420
421	public void deregisterWithBookmark() {
422		if (this.bookmark != null) {
423			this.bookmark.setConversation(null);
424		}
425	}
426
427	public Bookmark getBookmark() {
428		return this.bookmark;
429	}
430
431	public Bitmap getImage(Context context, int size) {
432		if (mode == MODE_SINGLE) {
433			return getContact().getImage(size, context);
434		} else {
435			return UIHelper.getContactPicture(this, size, context, false);
436		}
437	}
438
439	public boolean hasDuplicateMessage(Message message) {
440		for (int i = this.getMessages().size() - 1; i >= 0; --i) {
441			if (this.messages.get(i).equals(message)) {
442				return true;
443			}
444		}
445		return false;
446	}
447
448	public void setMutedTill(long value) {
449		this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
450	}
451
452	public boolean isMuted() {
453		return SystemClock.elapsedRealtime() < this.getLongAttribute(
454				ATTRIBUTE_MUTED_TILL, 0);
455	}
456
457	public boolean setAttribute(String key, String value) {
458		try {
459			this.attributes.put(key, value);
460			return true;
461		} catch (JSONException e) {
462			return false;
463		}
464	}
465
466	public String getAttribute(String key) {
467		try {
468			return this.attributes.getString(key);
469		} catch (JSONException e) {
470			return null;
471		}
472	}
473
474	public int getIntAttribute(String key, int defaultValue) {
475		String value = this.getAttribute(key);
476		if (value == null) {
477			return defaultValue;
478		} else {
479			try {
480				return Integer.parseInt(value);
481			} catch (NumberFormatException e) {
482				return defaultValue;
483			}
484		}
485	}
486
487	public long getLongAttribute(String key, long defaultValue) {
488		String value = this.getAttribute(key);
489		if (value == null) {
490			return defaultValue;
491		} else {
492			try {
493				return Long.parseLong(value);
494			} catch (NumberFormatException e) {
495				return defaultValue;
496			}
497		}
498	}
499	
500	public void add(Message message) {
501		message.setConversation(this);
502		synchronized (this.messages) {
503			this.messages.add(message);
504		}
505	}
506
507	public void addAll(int index, List<Message> messages) {
508		synchronized (this.messages) {
509			this.messages.addAll(index, messages);
510		}
511	}
512}