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