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