1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5import android.os.SystemClock;
6import android.util.Log;
7import androidx.annotation.NonNull;
8import com.google.common.base.Strings;
9import com.google.common.collect.ImmutableList;
10import eu.siacs.conversations.Config;
11import eu.siacs.conversations.R;
12import eu.siacs.conversations.crypto.PgpDecryptionService;
13import eu.siacs.conversations.crypto.axolotl.AxolotlService;
14import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
15import eu.siacs.conversations.crypto.sasl.ChannelBinding;
16import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism;
17import eu.siacs.conversations.crypto.sasl.HashedToken;
18import eu.siacs.conversations.crypto.sasl.HashedTokenSha256;
19import eu.siacs.conversations.crypto.sasl.HashedTokenSha512;
20import eu.siacs.conversations.crypto.sasl.SaslMechanism;
21import eu.siacs.conversations.services.AvatarService;
22import eu.siacs.conversations.services.XmppConnectionService;
23import eu.siacs.conversations.utils.UIHelper;
24import eu.siacs.conversations.utils.XmppUri;
25import eu.siacs.conversations.xmpp.Jid;
26import eu.siacs.conversations.xmpp.XmppConnection;
27import eu.siacs.conversations.xmpp.jingle.RtpCapability;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35import java.util.concurrent.CopyOnWriteArraySet;
36import org.json.JSONException;
37import org.json.JSONObject;
38
39public class Account extends AbstractEntity implements AvatarService.Avatarable {
40
41 public static final String TABLENAME = "accounts";
42
43 public static final String USERNAME = "username";
44 public static final String SERVER = "server";
45 public static final String PASSWORD = "password";
46 public static final String OPTIONS = "options";
47 public static final String ROSTERVERSION = "rosterversion";
48 public static final String KEYS = "keys";
49 public static final String AVATAR = "avatar";
50 public static final String DISPLAY_NAME = "display_name";
51 public static final String HOSTNAME = "hostname";
52 public static final String PORT = "port";
53 public static final String STATUS = "status";
54 public static final String STATUS_MESSAGE = "status_message";
55 public static final String RESOURCE = "resource";
56 public static final String PINNED_MECHANISM = "pinned_mechanism";
57 public static final String PINNED_CHANNEL_BINDING = "pinned_channel_binding";
58 public static final String FAST_MECHANISM = "fast_mechanism";
59 public static final String FAST_TOKEN = "fast_token";
60
61 public static final int OPTION_DISABLED = 1;
62 public static final int OPTION_REGISTER = 2;
63 public static final int OPTION_MAGIC_CREATE = 4;
64 public static final int OPTION_REQUIRES_ACCESS_MODE_CHANGE = 5;
65 public static final int OPTION_LOGGED_IN_SUCCESSFULLY = 6;
66 public static final int OPTION_HTTP_UPLOAD_AVAILABLE = 7;
67 public static final int OPTION_UNVERIFIED = 8;
68 public static final int OPTION_FIXED_USERNAME = 9;
69 public static final int OPTION_QUICKSTART_AVAILABLE = 10;
70 public static final int OPTION_SOFT_DISABLED = 11;
71
72 private static final String KEY_PGP_SIGNATURE = "pgp_signature";
73 private static final String KEY_PGP_ID = "pgp_id";
74 private static final String KEY_PINNED_MECHANISM = "pinned_mechanism";
75 public static final String KEY_PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration";
76
77 protected final JSONObject keys;
78 private final Roster roster = new Roster(this);
79 private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
80 public final Set<Conversation> pendingConferenceJoins = new HashSet<>();
81 public final Set<Conversation> pendingConferenceLeaves = new HashSet<>();
82 public final Set<Conversation> inProgressConferenceJoins = new HashSet<>();
83 public final Set<Conversation> inProgressConferencePings = new HashSet<>();
84 protected Jid jid;
85 protected String password;
86 protected int options = 0;
87 protected State status = State.OFFLINE;
88 private State lastErrorStatus = State.OFFLINE;
89 protected String resource;
90 protected String avatar;
91 protected String hostname = null;
92 protected int port = 5222;
93 protected boolean online = false;
94 private String rosterVersion;
95 private String displayName = null;
96 private AxolotlService axolotlService = null;
97 private PgpDecryptionService pgpDecryptionService = null;
98 private XmppConnection xmppConnection = null;
99 private long mEndGracePeriod = 0L;
100 private final Map<Jid, Bookmark> bookmarks = new HashMap<>();
101 private Presence.Status presenceStatus;
102 private String presenceStatusMessage;
103 private String pinnedMechanism;
104 private String pinnedChannelBinding;
105 private String fastMechanism;
106 private String fastToken;
107
108 public Account(final Jid jid, final String password) {
109 this(
110 java.util.UUID.randomUUID().toString(),
111 jid,
112 password,
113 0,
114 null,
115 "",
116 null,
117 null,
118 null,
119 5222,
120 Presence.Status.ONLINE,
121 null,
122 null,
123 null,
124 null,
125 null);
126 }
127
128 private Account(
129 final String uuid,
130 final Jid jid,
131 final String password,
132 final int options,
133 final String rosterVersion,
134 final String keys,
135 final String avatar,
136 String displayName,
137 String hostname,
138 int port,
139 final Presence.Status status,
140 String statusMessage,
141 final String pinnedMechanism,
142 final String pinnedChannelBinding,
143 final String fastMechanism,
144 final String fastToken) {
145 this.uuid = uuid;
146 this.jid = jid;
147 this.password = password;
148 this.options = options;
149 this.rosterVersion = rosterVersion;
150 JSONObject tmp;
151 try {
152 tmp = new JSONObject(keys);
153 } catch (JSONException e) {
154 tmp = new JSONObject();
155 }
156 this.keys = tmp;
157 this.avatar = avatar;
158 this.displayName = displayName;
159 this.hostname = hostname;
160 this.port = port;
161 this.presenceStatus = status;
162 this.presenceStatusMessage = statusMessage;
163 this.pinnedMechanism = pinnedMechanism;
164 this.pinnedChannelBinding = pinnedChannelBinding;
165 this.fastMechanism = fastMechanism;
166 this.fastToken = fastToken;
167 }
168
169 public static Account fromCursor(final Cursor cursor) {
170 final Jid jid;
171 try {
172 final String resource = cursor.getString(cursor.getColumnIndexOrThrow(RESOURCE));
173 jid =
174 Jid.of(
175 cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)),
176 cursor.getString(cursor.getColumnIndexOrThrow(SERVER)),
177 resource == null || resource.trim().isEmpty() ? null : resource);
178 } catch (final IllegalArgumentException e) {
179 Log.d(
180 Config.LOGTAG,
181 cursor.getString(cursor.getColumnIndexOrThrow(USERNAME))
182 + "@"
183 + cursor.getString(cursor.getColumnIndexOrThrow(SERVER)));
184 throw new AssertionError(e);
185 }
186 return new Account(
187 cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
188 jid,
189 cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD)),
190 cursor.getInt(cursor.getColumnIndexOrThrow(OPTIONS)),
191 cursor.getString(cursor.getColumnIndexOrThrow(ROSTERVERSION)),
192 cursor.getString(cursor.getColumnIndexOrThrow(KEYS)),
193 cursor.getString(cursor.getColumnIndexOrThrow(AVATAR)),
194 cursor.getString(cursor.getColumnIndexOrThrow(DISPLAY_NAME)),
195 cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)),
196 cursor.getInt(cursor.getColumnIndexOrThrow(PORT)),
197 Presence.Status.fromShowString(
198 cursor.getString(cursor.getColumnIndexOrThrow(STATUS))),
199 cursor.getString(cursor.getColumnIndexOrThrow(STATUS_MESSAGE)),
200 cursor.getString(cursor.getColumnIndexOrThrow(PINNED_MECHANISM)),
201 cursor.getString(cursor.getColumnIndexOrThrow(PINNED_CHANNEL_BINDING)),
202 cursor.getString(cursor.getColumnIndexOrThrow(FAST_MECHANISM)),
203 cursor.getString(cursor.getColumnIndexOrThrow(FAST_TOKEN)));
204 }
205
206 public boolean httpUploadAvailable(long size) {
207 return xmppConnection != null && xmppConnection.getFeatures().httpUpload(size);
208 }
209
210 public boolean httpUploadAvailable() {
211 return isOptionSet(OPTION_HTTP_UPLOAD_AVAILABLE) || httpUploadAvailable(0);
212 }
213
214 public String getDisplayName() {
215 return displayName;
216 }
217
218 public void setDisplayName(String displayName) {
219 this.displayName = displayName;
220 }
221
222 public Contact getSelfContact() {
223 return getRoster().getContact(jid);
224 }
225
226 public boolean hasPendingPgpIntent(Conversation conversation) {
227 return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation);
228 }
229
230 public boolean isPgpDecryptionServiceConnected() {
231 return pgpDecryptionService != null && pgpDecryptionService.isConnected();
232 }
233
234 public boolean setShowErrorNotification(boolean newValue) {
235 boolean oldValue = showErrorNotification();
236 setKey("show_error", Boolean.toString(newValue));
237 return newValue != oldValue;
238 }
239
240 public boolean showErrorNotification() {
241 String key = getKey("show_error");
242 return key == null || Boolean.parseBoolean(key);
243 }
244
245 public boolean isEnabled() {
246 return !isOptionSet(Account.OPTION_DISABLED);
247 }
248
249 public boolean isConnectionEnabled() {
250 return !isOptionSet(Account.OPTION_DISABLED) && !isOptionSet(Account.OPTION_SOFT_DISABLED);
251 }
252
253 public boolean isOptionSet(final int option) {
254 return ((options & (1 << option)) != 0);
255 }
256
257 public boolean setOption(final int option, final boolean value) {
258 if (value && (option == OPTION_DISABLED || option == OPTION_SOFT_DISABLED)) {
259 this.setStatus(State.OFFLINE);
260 }
261 final int before = this.options;
262 if (value) {
263 this.options |= 1 << option;
264 } else {
265 this.options &= ~(1 << option);
266 }
267 return before != this.options;
268 }
269
270 public String getUsername() {
271 return jid.getLocal();
272 }
273
274 public boolean setJid(final Jid next) {
275 final Jid previousFull = this.jid;
276 final Jid prev = this.jid != null ? this.jid.asBareJid() : null;
277 final boolean changed = prev == null || (next != null && !prev.equals(next.asBareJid()));
278 if (changed) {
279 final AxolotlService oldAxolotlService = this.axolotlService;
280 if (oldAxolotlService != null) {
281 oldAxolotlService.destroy();
282 this.jid = next;
283 this.axolotlService = oldAxolotlService.makeNew();
284 }
285 }
286 this.jid = next;
287 return next != null && !next.equals(previousFull);
288 }
289
290 public Jid getDomain() {
291 return jid.getDomain();
292 }
293
294 public String getServer() {
295 return jid.getDomain().toString();
296 }
297
298 public String getPassword() {
299 return password;
300 }
301
302 public void setPassword(final String password) {
303 this.password = password;
304 }
305
306 @NonNull
307 public String getHostname() {
308 return Strings.nullToEmpty(this.hostname);
309 }
310
311 public void setHostname(final String hostname) {
312 this.hostname = hostname;
313 }
314
315 public boolean isOnion() {
316 final String server = getServer();
317 return server != null && server.endsWith(".onion");
318 }
319
320 public boolean isDirectToOnion() {
321 final var hostname = Strings.nullToEmpty(this.hostname).trim();
322 return isOnion() && (hostname.isEmpty() || hostname.endsWith(".onion"));
323 }
324
325 public int getPort() {
326 return this.port;
327 }
328
329 public void setPort(int port) {
330 this.port = port;
331 }
332
333 public State getStatus() {
334 if (isOptionSet(OPTION_DISABLED)) {
335 return State.DISABLED;
336 } else if (isOptionSet(OPTION_SOFT_DISABLED)) {
337 return State.LOGGED_OUT;
338 } else {
339 return this.status;
340 }
341 }
342
343 public boolean unauthorized() {
344 return this.status == State.UNAUTHORIZED || this.lastErrorStatus == State.UNAUTHORIZED;
345 }
346
347 public State getLastErrorStatus() {
348 return this.lastErrorStatus;
349 }
350
351 public void setStatus(final State status) {
352 this.status = status;
353 if (status.isError || status == State.ONLINE) {
354 this.lastErrorStatus = status;
355 }
356 }
357
358 public void setPinnedMechanism(final SaslMechanism mechanism) {
359 this.pinnedMechanism = mechanism.getMechanism();
360 if (mechanism instanceof ChannelBindingMechanism) {
361 this.pinnedChannelBinding =
362 ((ChannelBindingMechanism) mechanism).getChannelBinding().toString();
363 } else {
364 this.pinnedChannelBinding = null;
365 }
366 }
367
368 public void setFastToken(final HashedToken.Mechanism mechanism, final String token) {
369 this.fastMechanism = mechanism.name();
370 this.fastToken = token;
371 }
372
373 public void resetFastToken() {
374 this.fastMechanism = null;
375 this.fastToken = null;
376 }
377
378 public void resetPinnedMechanism() {
379 this.pinnedMechanism = null;
380 this.pinnedChannelBinding = null;
381 setKey(Account.KEY_PINNED_MECHANISM, String.valueOf(-1));
382 }
383
384 public int getPinnedMechanismPriority() {
385 final int fallback = getKeyAsInt(KEY_PINNED_MECHANISM, -1);
386 if (Strings.isNullOrEmpty(this.pinnedMechanism)) {
387 return fallback;
388 }
389 final SaslMechanism saslMechanism = getPinnedMechanism();
390 if (saslMechanism == null) {
391 return fallback;
392 } else {
393 return saslMechanism.getPriority();
394 }
395 }
396
397 private SaslMechanism getPinnedMechanism() {
398 final String mechanism = Strings.nullToEmpty(this.pinnedMechanism);
399 final ChannelBinding channelBinding = ChannelBinding.get(this.pinnedChannelBinding);
400 return new SaslMechanism.Factory(this).of(mechanism, channelBinding);
401 }
402
403 public HashedToken getFastMechanism() {
404 final HashedToken.Mechanism fastMechanism =
405 HashedToken.Mechanism.ofOrNull(this.fastMechanism);
406 final String token = this.fastToken;
407 if (fastMechanism == null || Strings.isNullOrEmpty(token)) {
408 return null;
409 }
410 if (fastMechanism.hashFunction.equals("SHA-256")) {
411 return new HashedTokenSha256(this, fastMechanism.channelBinding);
412 } else if (fastMechanism.hashFunction.equals("SHA-512")) {
413 return new HashedTokenSha512(this, fastMechanism.channelBinding);
414 } else {
415 return null;
416 }
417 }
418
419 public SaslMechanism getQuickStartMechanism() {
420 final HashedToken hashedTokenMechanism = getFastMechanism();
421 if (hashedTokenMechanism != null) {
422 return hashedTokenMechanism;
423 }
424 return getPinnedMechanism();
425 }
426
427 public String getFastToken() {
428 return this.fastToken;
429 }
430
431 public State getTrueStatus() {
432 return this.status;
433 }
434
435 public boolean errorStatus() {
436 return getStatus().isError();
437 }
438
439 public boolean hasErrorStatus() {
440 return getXmppConnection() != null
441 && (getStatus().isError() || getStatus() == State.CONNECTING)
442 && getXmppConnection().getAttempt() >= 3;
443 }
444
445 public Presence.Status getPresenceStatus() {
446 return this.presenceStatus;
447 }
448
449 public void setPresenceStatus(Presence.Status status) {
450 this.presenceStatus = status;
451 }
452
453 public String getPresenceStatusMessage() {
454 return this.presenceStatusMessage;
455 }
456
457 public void setPresenceStatusMessage(String message) {
458 this.presenceStatusMessage = message;
459 }
460
461 public String getResource() {
462 return jid.getResource();
463 }
464
465 public void setResource(final String resource) {
466 this.jid = this.jid.withResource(resource);
467 }
468
469 public Jid getJid() {
470 return jid;
471 }
472
473 public JSONObject getKeys() {
474 return keys;
475 }
476
477 public String getKey(final String name) {
478 synchronized (this.keys) {
479 return this.keys.optString(name, null);
480 }
481 }
482
483 public int getKeyAsInt(final String name, int defaultValue) {
484 String key = getKey(name);
485 try {
486 return key == null ? defaultValue : Integer.parseInt(key);
487 } catch (NumberFormatException e) {
488 return defaultValue;
489 }
490 }
491
492 public boolean setKey(final String keyName, final String keyValue) {
493 synchronized (this.keys) {
494 try {
495 this.keys.put(keyName, keyValue);
496 return true;
497 } catch (final JSONException e) {
498 return false;
499 }
500 }
501 }
502
503 public void setPrivateKeyAlias(final String alias) {
504 setKey("private_key_alias", alias);
505 }
506
507 public String getPrivateKeyAlias() {
508 return getKey("private_key_alias");
509 }
510
511 @Override
512 public ContentValues getContentValues() {
513 final ContentValues values = new ContentValues();
514 values.put(UUID, uuid);
515 values.put(USERNAME, jid.getLocal());
516 values.put(SERVER, jid.getDomain().toString());
517 values.put(PASSWORD, password);
518 values.put(OPTIONS, options);
519 synchronized (this.keys) {
520 values.put(KEYS, this.keys.toString());
521 }
522 values.put(ROSTERVERSION, rosterVersion);
523 values.put(AVATAR, avatar);
524 values.put(DISPLAY_NAME, displayName);
525 values.put(HOSTNAME, hostname);
526 values.put(PORT, port);
527 values.put(STATUS, presenceStatus.toShowString());
528 values.put(STATUS_MESSAGE, presenceStatusMessage);
529 values.put(RESOURCE, jid.getResource());
530 values.put(PINNED_MECHANISM, pinnedMechanism);
531 values.put(PINNED_CHANNEL_BINDING, pinnedChannelBinding);
532 values.put(FAST_MECHANISM, this.fastMechanism);
533 values.put(FAST_TOKEN, this.fastToken);
534 return values;
535 }
536
537 public AxolotlService getAxolotlService() {
538 return axolotlService;
539 }
540
541 public void initAccountServices(final XmppConnectionService context) {
542 this.axolotlService = new AxolotlService(this, context);
543 this.pgpDecryptionService = new PgpDecryptionService(context);
544 if (xmppConnection != null) {
545 xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
546 }
547 }
548
549 public PgpDecryptionService getPgpDecryptionService() {
550 return this.pgpDecryptionService;
551 }
552
553 public XmppConnection getXmppConnection() {
554 return this.xmppConnection;
555 }
556
557 public void setXmppConnection(final XmppConnection connection) {
558 this.xmppConnection = connection;
559 }
560
561 public String getRosterVersion() {
562 if (this.rosterVersion == null) {
563 return "";
564 } else {
565 return this.rosterVersion;
566 }
567 }
568
569 public void setRosterVersion(final String version) {
570 this.rosterVersion = version;
571 }
572
573 public int countPresences() {
574 return this.getSelfContact().getPresences().size();
575 }
576
577 public int activeDevicesWithRtpCapability() {
578 int i = 0;
579 for (Presence presence : getSelfContact().getPresences().getPresences()) {
580 if (RtpCapability.check(presence) != RtpCapability.Capability.NONE) {
581 i++;
582 }
583 }
584 return i;
585 }
586
587 public String getPgpSignature() {
588 return getKey(KEY_PGP_SIGNATURE);
589 }
590
591 public boolean setPgpSignature(String signature) {
592 return setKey(KEY_PGP_SIGNATURE, signature);
593 }
594
595 public boolean unsetPgpSignature() {
596 synchronized (this.keys) {
597 return keys.remove(KEY_PGP_SIGNATURE) != null;
598 }
599 }
600
601 public long getPgpId() {
602 synchronized (this.keys) {
603 if (keys.has(KEY_PGP_ID)) {
604 try {
605 return keys.getLong(KEY_PGP_ID);
606 } catch (JSONException e) {
607 return 0;
608 }
609 } else {
610 return 0;
611 }
612 }
613 }
614
615 public boolean setPgpSignId(long pgpID) {
616 synchronized (this.keys) {
617 try {
618 if (pgpID == 0) {
619 keys.remove(KEY_PGP_ID);
620 } else {
621 keys.put(KEY_PGP_ID, pgpID);
622 }
623 } catch (JSONException e) {
624 return false;
625 }
626 return true;
627 }
628 }
629
630 public Roster getRoster() {
631 return this.roster;
632 }
633
634 public Collection<Bookmark> getBookmarks() {
635 synchronized (this.bookmarks) {
636 return ImmutableList.copyOf(this.bookmarks.values());
637 }
638 }
639
640 public void setBookmarks(final Map<Jid, Bookmark> bookmarks) {
641 synchronized (this.bookmarks) {
642 this.bookmarks.clear();
643 this.bookmarks.putAll(bookmarks);
644 }
645 }
646
647 public void putBookmark(final Bookmark bookmark) {
648 synchronized (this.bookmarks) {
649 this.bookmarks.put(bookmark.getJid(), bookmark);
650 }
651 }
652
653 public void removeBookmark(Bookmark bookmark) {
654 synchronized (this.bookmarks) {
655 this.bookmarks.remove(bookmark.getJid());
656 }
657 }
658
659 public void removeBookmark(Jid jid) {
660 synchronized (this.bookmarks) {
661 this.bookmarks.remove(jid);
662 }
663 }
664
665 public Set<Jid> getBookmarkedJids() {
666 synchronized (this.bookmarks) {
667 return new HashSet<>(this.bookmarks.keySet());
668 }
669 }
670
671 public Bookmark getBookmark(final Jid jid) {
672 synchronized (this.bookmarks) {
673 return this.bookmarks.get(jid.asBareJid());
674 }
675 }
676
677 public boolean setAvatar(final String filename) {
678 if (this.avatar != null && this.avatar.equals(filename)) {
679 return false;
680 } else {
681 this.avatar = filename;
682 return true;
683 }
684 }
685
686 public String getAvatar() {
687 return this.avatar;
688 }
689
690 public void activateGracePeriod(final long duration) {
691 if (duration > 0) {
692 this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
693 }
694 }
695
696 public void deactivateGracePeriod() {
697 this.mEndGracePeriod = 0L;
698 }
699
700 public boolean inGracePeriod() {
701 return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
702 }
703
704 public String getShareableUri() {
705 List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
706 final String uri = "xmpp:" + this.getJid().asBareJid().toString();
707 if (fingerprints.isEmpty()) {
708 return uri;
709 } else {
710 return XmppUri.getFingerprintUri(uri, fingerprints, ';');
711 }
712 }
713
714 public String getShareableLink() {
715 List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
716 String uri =
717 "https://conversations.im/i/"
718 + XmppUri.lameUrlEncode(this.getJid().asBareJid().toString());
719 if (fingerprints.isEmpty()) {
720 return uri;
721 } else {
722 return XmppUri.getFingerprintUri(uri, fingerprints, '&');
723 }
724 }
725
726 private List<XmppUri.Fingerprint> getFingerprints() {
727 ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
728 if (axolotlService == null) {
729 return fingerprints;
730 }
731 fingerprints.add(
732 new XmppUri.Fingerprint(
733 XmppUri.FingerprintType.OMEMO,
734 axolotlService.getOwnFingerprint().substring(2),
735 axolotlService.getOwnDeviceId()));
736 for (XmppAxolotlSession session : axolotlService.findOwnSessions()) {
737 if (session.getTrust().isVerified() && session.getTrust().isActive()) {
738 fingerprints.add(
739 new XmppUri.Fingerprint(
740 XmppUri.FingerprintType.OMEMO,
741 session.getFingerprint().substring(2).replaceAll("\\s", ""),
742 session.getRemoteAddress().getDeviceId()));
743 }
744 }
745 return fingerprints;
746 }
747
748 public boolean isBlocked(final ListItem contact) {
749 final Jid jid = contact.getJid();
750 return jid != null
751 && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain()));
752 }
753
754 public boolean isBlocked(final Jid jid) {
755 return jid != null && blocklist.contains(jid.asBareJid());
756 }
757
758 public Collection<Jid> getBlocklist() {
759 return this.blocklist;
760 }
761
762 public void clearBlocklist() {
763 getBlocklist().clear();
764 }
765
766 public boolean isOnlineAndConnected() {
767 return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
768 }
769
770 @Override
771 public int getAvatarBackgroundColor() {
772 return UIHelper.getColorForName(jid.asBareJid().toString());
773 }
774
775 @Override
776 public String getAvatarName() {
777 throw new IllegalStateException("This method should not be called");
778 }
779
780 public enum State {
781 DISABLED(false, false),
782 LOGGED_OUT(false, false),
783 OFFLINE(false),
784 CONNECTING(false),
785 ONLINE(false),
786 NO_INTERNET(false),
787 CONNECTION_TIMEOUT,
788 UNAUTHORIZED,
789 TEMPORARY_AUTH_FAILURE,
790 SERVER_NOT_FOUND,
791 REGISTRATION_SUCCESSFUL(false),
792 REGISTRATION_FAILED(true, false),
793 REGISTRATION_WEB(true, false),
794 REGISTRATION_CONFLICT(true, false),
795 REGISTRATION_NOT_SUPPORTED(true, false),
796 REGISTRATION_PLEASE_WAIT(true, false),
797 REGISTRATION_INVALID_TOKEN(true, false),
798 REGISTRATION_PASSWORD_TOO_WEAK(true, false),
799 TLS_ERROR,
800 TLS_ERROR_DOMAIN,
801 CHANNEL_BINDING,
802 INCOMPATIBLE_SERVER,
803 INCOMPATIBLE_CLIENT,
804 TOR_NOT_AVAILABLE,
805 DOWNGRADE_ATTACK,
806 SESSION_FAILURE,
807 BIND_FAILURE,
808 HOST_UNKNOWN,
809 STREAM_ERROR,
810 SEE_OTHER_HOST,
811 STREAM_OPENING_ERROR,
812 POLICY_VIOLATION,
813 PAYMENT_REQUIRED,
814 MISSING_INTERNET_PERMISSION(false);
815
816 private final boolean isError;
817 private final boolean attemptReconnect;
818
819 State(final boolean isError) {
820 this(isError, true);
821 }
822
823 State(final boolean isError, final boolean reconnect) {
824 this.isError = isError;
825 this.attemptReconnect = reconnect;
826 }
827
828 State() {
829 this(true, true);
830 }
831
832 public boolean isError() {
833 return this.isError;
834 }
835
836 public boolean isAttemptReconnect() {
837 return this.attemptReconnect;
838 }
839
840 public int getReadableId() {
841 switch (this) {
842 case DISABLED:
843 return R.string.account_status_disabled;
844 case LOGGED_OUT:
845 return R.string.account_state_logged_out;
846 case ONLINE:
847 return R.string.account_status_online;
848 case CONNECTING:
849 return R.string.account_status_connecting;
850 case OFFLINE:
851 return R.string.account_status_offline;
852 case UNAUTHORIZED:
853 return R.string.account_status_unauthorized;
854 case SERVER_NOT_FOUND:
855 return R.string.account_status_not_found;
856 case NO_INTERNET:
857 return R.string.account_status_no_internet;
858 case CONNECTION_TIMEOUT:
859 return R.string.account_status_connection_timeout;
860 case REGISTRATION_FAILED:
861 return R.string.account_status_regis_fail;
862 case REGISTRATION_WEB:
863 return R.string.account_status_regis_web;
864 case REGISTRATION_CONFLICT:
865 return R.string.account_status_regis_conflict;
866 case REGISTRATION_SUCCESSFUL:
867 return R.string.account_status_regis_success;
868 case REGISTRATION_NOT_SUPPORTED:
869 return R.string.account_status_regis_not_sup;
870 case REGISTRATION_INVALID_TOKEN:
871 return R.string.account_status_regis_invalid_token;
872 case TLS_ERROR:
873 return R.string.account_status_tls_error;
874 case TLS_ERROR_DOMAIN:
875 return R.string.account_status_tls_error_domain;
876 case INCOMPATIBLE_SERVER:
877 return R.string.account_status_incompatible_server;
878 case INCOMPATIBLE_CLIENT:
879 return R.string.account_status_incompatible_client;
880 case CHANNEL_BINDING:
881 return R.string.account_status_channel_binding;
882 case TOR_NOT_AVAILABLE:
883 return R.string.account_status_tor_unavailable;
884 case BIND_FAILURE:
885 return R.string.account_status_bind_failure;
886 case SESSION_FAILURE:
887 return R.string.session_failure;
888 case DOWNGRADE_ATTACK:
889 return R.string.sasl_downgrade;
890 case HOST_UNKNOWN:
891 return R.string.account_status_host_unknown;
892 case POLICY_VIOLATION:
893 return R.string.account_status_policy_violation;
894 case REGISTRATION_PLEASE_WAIT:
895 return R.string.registration_please_wait;
896 case REGISTRATION_PASSWORD_TOO_WEAK:
897 return R.string.registration_password_too_weak;
898 case STREAM_ERROR:
899 return R.string.account_status_stream_error;
900 case STREAM_OPENING_ERROR:
901 return R.string.account_status_stream_opening_error;
902 case PAYMENT_REQUIRED:
903 return R.string.payment_required;
904 case SEE_OTHER_HOST:
905 return R.string.reconnect_on_other_host;
906 case MISSING_INTERNET_PERMISSION:
907 return R.string.missing_internet_permission;
908 case TEMPORARY_AUTH_FAILURE:
909 return R.string.account_status_temporary_auth_failure;
910 default:
911 return R.string.account_status_unknown;
912 }
913 }
914 }
915}