Account.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5import android.os.SystemClock;
  6
  7import eu.siacs.conversations.crypto.PgpDecryptionService;
  8import net.java.otr4j.crypto.OtrCryptoEngineImpl;
  9import net.java.otr4j.crypto.OtrCryptoException;
 10
 11import org.json.JSONException;
 12import org.json.JSONObject;
 13
 14import java.security.PublicKey;
 15import java.security.interfaces.DSAPublicKey;
 16import java.util.Collection;
 17import java.util.List;
 18import java.util.concurrent.CopyOnWriteArrayList;
 19import java.util.concurrent.CopyOnWriteArraySet;
 20
 21import eu.siacs.conversations.Config;
 22import eu.siacs.conversations.R;
 23import eu.siacs.conversations.crypto.OtrService;
 24import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 25import eu.siacs.conversations.services.XmppConnectionService;
 26import eu.siacs.conversations.xmpp.XmppConnection;
 27import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 28import eu.siacs.conversations.xmpp.jid.Jid;
 29
 30public class Account extends AbstractEntity {
 31
 32	public static final String TABLENAME = "accounts";
 33
 34	public static final String USERNAME = "username";
 35	public static final String SERVER = "server";
 36	public static final String PASSWORD = "password";
 37	public static final String OPTIONS = "options";
 38	public static final String ROSTERVERSION = "rosterversion";
 39	public static final String KEYS = "keys";
 40	public static final String AVATAR = "avatar";
 41	public static final String DISPLAY_NAME = "display_name";
 42
 43	public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
 44
 45	public static final int OPTION_USETLS = 0;
 46	public static final int OPTION_DISABLED = 1;
 47	public static final int OPTION_REGISTER = 2;
 48	public static final int OPTION_USECOMPRESSION = 3;
 49
 50	public boolean httpUploadAvailable() {
 51		return xmppConnection != null && xmppConnection.getFeatures().httpUpload();
 52	}
 53
 54	public void setDisplayName(String displayName) {
 55		this.displayName = displayName;
 56	}
 57
 58	public String getDisplayName() {
 59		return displayName;
 60	}
 61
 62	public XmppConnection.Identity getServerIdentity() {
 63		if (xmppConnection == null) {
 64			return XmppConnection.Identity.UNKNOWN;
 65		} else {
 66			return xmppConnection.getServerIdentity();
 67		}
 68	}
 69
 70	public static enum State {
 71		DISABLED,
 72		OFFLINE,
 73		CONNECTING,
 74		ONLINE,
 75		NO_INTERNET,
 76		UNAUTHORIZED(true),
 77		SERVER_NOT_FOUND(true),
 78		REGISTRATION_FAILED(true),
 79		REGISTRATION_CONFLICT(true),
 80		REGISTRATION_SUCCESSFUL,
 81		REGISTRATION_NOT_SUPPORTED(true),
 82		SECURITY_ERROR(true),
 83		INCOMPATIBLE_SERVER(true),
 84		DNS_TIMEOUT(true);
 85
 86		private final boolean isError;
 87
 88		public boolean isError() {
 89			return this.isError;
 90		}
 91
 92		private State(final boolean isError) {
 93			this.isError = isError;
 94		}
 95
 96		private State() {
 97			this(false);
 98		}
 99
100		public int getReadableId() {
101			switch (this) {
102				case DISABLED:
103					return R.string.account_status_disabled;
104				case ONLINE:
105					return R.string.account_status_online;
106				case CONNECTING:
107					return R.string.account_status_connecting;
108				case OFFLINE:
109					return R.string.account_status_offline;
110				case UNAUTHORIZED:
111					return R.string.account_status_unauthorized;
112				case SERVER_NOT_FOUND:
113					return R.string.account_status_not_found;
114				case NO_INTERNET:
115					return R.string.account_status_no_internet;
116				case REGISTRATION_FAILED:
117					return R.string.account_status_regis_fail;
118				case REGISTRATION_CONFLICT:
119					return R.string.account_status_regis_conflict;
120				case REGISTRATION_SUCCESSFUL:
121					return R.string.account_status_regis_success;
122				case REGISTRATION_NOT_SUPPORTED:
123					return R.string.account_status_regis_not_sup;
124				case SECURITY_ERROR:
125					return R.string.account_status_security_error;
126				case INCOMPATIBLE_SERVER:
127					return R.string.account_status_incompatible_server;
128				case DNS_TIMEOUT:
129					return R.string.account_status_dns_timeout;
130				default:
131					return R.string.account_status_unknown;
132			}
133		}
134	}
135
136	public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>();
137	public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>();
138
139	private static final String KEY_PGP_SIGNATURE = "pgp_signature";
140	private static final String KEY_PGP_ID = "pgp_id";
141
142	protected Jid jid;
143	protected String password;
144	protected int options = 0;
145	protected String rosterVersion;
146	protected State status = State.OFFLINE;
147	protected JSONObject keys = new JSONObject();
148	protected String avatar;
149	protected String displayName = null;
150	protected boolean online = false;
151	private OtrService mOtrService = null;
152	private AxolotlService axolotlService = null;
153	private PgpDecryptionService pgpDecryptionService = null;
154	private XmppConnection xmppConnection = null;
155	private long mEndGracePeriod = 0L;
156	private String otrFingerprint;
157	private final Roster roster = new Roster(this);
158	private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
159	private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
160
161	public Account() {
162		this.uuid = "0";
163	}
164
165	public Account(final Jid jid, final String password) {
166		this(java.util.UUID.randomUUID().toString(), jid,
167				password, 0, null, "", null, null);
168	}
169
170	public Account(final String uuid, final Jid jid,
171			final String password, final int options, final String rosterVersion, final String keys,
172			final String avatar, String displayName) {
173		this.uuid = uuid;
174		this.jid = jid;
175		if (jid.isBareJid()) {
176			this.setResource("mobile");
177		}
178		this.password = password;
179		this.options = options;
180		this.rosterVersion = rosterVersion;
181		try {
182			this.keys = new JSONObject(keys);
183		} catch (final JSONException ignored) {
184			this.keys = new JSONObject();
185		}
186		this.avatar = avatar;
187		this.displayName = displayName;
188	}
189
190	public static Account fromCursor(final Cursor cursor) {
191		Jid jid = null;
192		try {
193			jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)),
194					cursor.getString(cursor.getColumnIndex(SERVER)), "mobile");
195		} catch (final InvalidJidException ignored) {
196		}
197		return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
198				jid,
199				cursor.getString(cursor.getColumnIndex(PASSWORD)),
200				cursor.getInt(cursor.getColumnIndex(OPTIONS)),
201				cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
202				cursor.getString(cursor.getColumnIndex(KEYS)),
203				cursor.getString(cursor.getColumnIndex(AVATAR)),
204				cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
205	}
206
207	public boolean isOptionSet(final int option) {
208		return ((options & (1 << option)) != 0);
209	}
210
211	public void setOption(final int option, final boolean value) {
212		if (value) {
213			this.options |= 1 << option;
214		} else {
215			this.options &= ~(1 << option);
216		}
217	}
218
219	public String getUsername() {
220		return jid.getLocalpart();
221	}
222
223	public void setJid(final Jid jid) {
224		this.jid = jid;
225	}
226
227	public Jid getServer() {
228		return jid.toDomainJid();
229	}
230
231	public String getPassword() {
232		return password;
233	}
234
235	public void setPassword(final String password) {
236		this.password = password;
237	}
238
239	public State getStatus() {
240		if (isOptionSet(OPTION_DISABLED)) {
241			return State.DISABLED;
242		} else {
243			return this.status;
244		}
245	}
246
247	public void setStatus(final State status) {
248		this.status = status;
249	}
250
251	public boolean errorStatus() {
252		return getStatus().isError();
253	}
254
255	public boolean hasErrorStatus() {
256		return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 2;
257	}
258
259	public String getResource() {
260		return jid.getResourcepart();
261	}
262
263	public boolean setResource(final String resource) {
264		final String oldResource = jid.getResourcepart();
265		if (oldResource == null || !oldResource.equals(resource)) {
266			try {
267				jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
268				return true;
269			} catch (final InvalidJidException ignored) {
270				return true;
271			}
272		}
273		return false;
274	}
275
276	public Jid getJid() {
277		return jid;
278	}
279
280	public JSONObject getKeys() {
281		return keys;
282	}
283
284	public String getKey(final String name) {
285		return this.keys.optString(name, null);
286	}
287
288	public boolean setKey(final String keyName, final String keyValue) {
289		try {
290			this.keys.put(keyName, keyValue);
291			return true;
292		} catch (final JSONException e) {
293			return false;
294		}
295	}
296
297	public boolean setPrivateKeyAlias(String alias) {
298		return setKey("private_key_alias", alias);
299	}
300
301	public String getPrivateKeyAlias() {
302		return getKey("private_key_alias");
303	}
304
305	@Override
306	public ContentValues getContentValues() {
307		final ContentValues values = new ContentValues();
308		values.put(UUID, uuid);
309		values.put(USERNAME, jid.getLocalpart());
310		values.put(SERVER, jid.getDomainpart());
311		values.put(PASSWORD, password);
312		values.put(OPTIONS, options);
313		values.put(KEYS, this.keys.toString());
314		values.put(ROSTERVERSION, rosterVersion);
315		values.put(AVATAR, avatar);
316		values.put(DISPLAY_NAME, displayName);
317		return values;
318	}
319
320	public AxolotlService getAxolotlService() {
321		return axolotlService;
322	}
323
324	public void initAccountServices(final XmppConnectionService context) {
325		this.mOtrService = new OtrService(context, this);
326		this.axolotlService = new AxolotlService(this, context);
327		if (xmppConnection != null) {
328			xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
329		}
330		this.pgpDecryptionService = new PgpDecryptionService(context);
331	}
332
333	public OtrService getOtrService() {
334		return this.mOtrService;
335	}
336
337	public PgpDecryptionService getPgpDecryptionService() {
338		return pgpDecryptionService;
339	}
340
341	public XmppConnection getXmppConnection() {
342		return this.xmppConnection;
343	}
344
345	public void setXmppConnection(final XmppConnection connection) {
346		this.xmppConnection = connection;
347	}
348
349	public String getOtrFingerprint() {
350		if (this.otrFingerprint == null) {
351			try {
352				if (this.mOtrService == null) {
353					return null;
354				}
355				final PublicKey publicKey = this.mOtrService.getPublicKey();
356				if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
357					return null;
358				}
359				this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
360				return this.otrFingerprint;
361			} catch (final OtrCryptoException ignored) {
362				return null;
363			}
364		} else {
365			return this.otrFingerprint;
366		}
367	}
368
369	public String getRosterVersion() {
370		if (this.rosterVersion == null) {
371			return "";
372		} else {
373			return this.rosterVersion;
374		}
375	}
376
377	public void setRosterVersion(final String version) {
378		this.rosterVersion = version;
379	}
380
381	public int countPresences() {
382		return this.getRoster().getContact(this.getJid().toBareJid()).getPresences().size();
383	}
384
385	public String getPgpSignature() {
386		if (keys.has(KEY_PGP_SIGNATURE)) {
387			try {
388				return keys.getString(KEY_PGP_SIGNATURE);
389			} catch (final JSONException e) {
390				return null;
391			}
392		} else {
393			return null;
394		}
395	}
396
397	public boolean setPgpSignature(String signature) {
398		try {
399			keys.put(KEY_PGP_SIGNATURE, signature);
400		} catch (JSONException e) {
401			return false;
402		}
403		return true;
404	}
405
406	public long getPgpId() {
407		if (keys.has(KEY_PGP_ID)) {
408			try {
409				return keys.getLong(KEY_PGP_ID);
410			} catch (JSONException e) {
411				return -1;
412			}
413		} else {
414			return -1;
415		}
416	}
417
418	public boolean setPgpSignId(long pgpID) {
419		try {
420			keys.put(KEY_PGP_ID, pgpID);
421		} catch (JSONException e) {
422			return false;
423		}
424		return true;
425	}
426
427	public Roster getRoster() {
428		return this.roster;
429	}
430
431	public List<Bookmark> getBookmarks() {
432		return this.bookmarks;
433	}
434
435	public void setBookmarks(final List<Bookmark> bookmarks) {
436		this.bookmarks = bookmarks;
437	}
438
439	public boolean hasBookmarkFor(final Jid conferenceJid) {
440		for (final Bookmark bookmark : this.bookmarks) {
441			final Jid jid = bookmark.getJid();
442			if (jid != null && jid.equals(conferenceJid.toBareJid())) {
443				return true;
444			}
445		}
446		return false;
447	}
448
449	public boolean setAvatar(final String filename) {
450		if (this.avatar != null && this.avatar.equals(filename)) {
451			return false;
452		} else {
453			this.avatar = filename;
454			return true;
455		}
456	}
457
458	public String getAvatar() {
459		return this.avatar;
460	}
461
462	public void activateGracePeriod() {
463		this.mEndGracePeriod = SystemClock.elapsedRealtime()
464			+ (Config.CARBON_GRACE_PERIOD * 1000);
465	}
466
467	public void deactivateGracePeriod() {
468		this.mEndGracePeriod = 0L;
469	}
470
471	public boolean inGracePeriod() {
472		return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
473	}
474
475	public String getShareableUri() {
476		final String fingerprint = this.getOtrFingerprint();
477		if (fingerprint != null) {
478			return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
479		} else {
480			return "xmpp:" + this.getJid().toBareJid().toString();
481		}
482	}
483
484	public boolean isBlocked(final ListItem contact) {
485		final Jid jid = contact.getJid();
486		return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid()));
487	}
488
489	public boolean isBlocked(final Jid jid) {
490		return jid != null && blocklist.contains(jid.toBareJid());
491	}
492
493	public Collection<Jid> getBlocklist() {
494		return this.blocklist;
495	}
496
497	public void clearBlocklist() {
498		getBlocklist().clear();
499	}
500
501	public boolean isOnlineAndConnected() {
502		return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
503	}
504}