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