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