Account.java

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