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