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