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 if (xmppConnection != null) {
633 return xmppConnection.getManager(RosterManager.class);
634 }
635 // TODO either return stub or always put XmppConnection into Account
636 return null;
637 }
638
639 public Collection<Bookmark> getBookmarks() {
640 synchronized (this.bookmarks) {
641 return ImmutableList.copyOf(this.bookmarks.values());
642 }
643 }
644
645 public void setBookmarks(final Map<Jid, Bookmark> bookmarks) {
646 synchronized (this.bookmarks) {
647 this.bookmarks.clear();
648 this.bookmarks.putAll(bookmarks);
649 }
650 }
651
652 public void putBookmark(final Bookmark bookmark) {
653 synchronized (this.bookmarks) {
654 this.bookmarks.put(bookmark.getJid(), bookmark);
655 }
656 }
657
658 public void removeBookmark(Bookmark bookmark) {
659 synchronized (this.bookmarks) {
660 this.bookmarks.remove(bookmark.getJid());
661 }
662 }
663
664 public void removeBookmark(Jid jid) {
665 synchronized (this.bookmarks) {
666 this.bookmarks.remove(jid);
667 }
668 }
669
670 public Set<Jid> getBookmarkedJids() {
671 synchronized (this.bookmarks) {
672 return new HashSet<>(this.bookmarks.keySet());
673 }
674 }
675
676 public Bookmark getBookmark(final Jid jid) {
677 synchronized (this.bookmarks) {
678 return this.bookmarks.get(jid.asBareJid());
679 }
680 }
681
682 public boolean setAvatar(final String filename) {
683 if (this.avatar != null && this.avatar.equals(filename)) {
684 return false;
685 } else {
686 this.avatar = filename;
687 return true;
688 }
689 }
690
691 public String getAvatar() {
692 return this.avatar;
693 }
694
695 public void activateGracePeriod(final long duration) {
696 if (duration > 0) {
697 this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
698 }
699 }
700
701 public void deactivateGracePeriod() {
702 this.mEndGracePeriod = 0L;
703 }
704
705 public boolean inGracePeriod() {
706 return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
707 }
708
709 public String getShareableUri() {
710 List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
711 final String uri = "xmpp:" + this.getJid().asBareJid().toString();
712 if (fingerprints.isEmpty()) {
713 return uri;
714 } else {
715 return XmppUri.getFingerprintUri(uri, fingerprints, ';');
716 }
717 }
718
719 public String getShareableLink() {
720 List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
721 String uri =
722 "https://conversations.im/i/"
723 + XmppUri.lameUrlEncode(this.getJid().asBareJid().toString());
724 if (fingerprints.isEmpty()) {
725 return uri;
726 } else {
727 return XmppUri.getFingerprintUri(uri, fingerprints, '&');
728 }
729 }
730
731 private List<XmppUri.Fingerprint> getFingerprints() {
732 ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
733 final var axolotlService = getAxolotlService();
734 fingerprints.add(
735 new XmppUri.Fingerprint(
736 XmppUri.FingerprintType.OMEMO,
737 axolotlService.getOwnFingerprint().substring(2),
738 axolotlService.getOwnDeviceId()));
739 for (XmppAxolotlSession session : axolotlService.findOwnSessions()) {
740 if (session.getTrust().isVerified() && session.getTrust().isActive()) {
741 fingerprints.add(
742 new XmppUri.Fingerprint(
743 XmppUri.FingerprintType.OMEMO,
744 session.getFingerprint().substring(2).replaceAll("\\s", ""),
745 session.getRemoteAddress().getDeviceId()));
746 }
747 }
748 return fingerprints;
749 }
750
751 public boolean isBlocked(final ListItem contact) {
752 final Jid jid = contact.getJid();
753 final var blocklist = getBlocklist();
754 return jid != null
755 && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain()));
756 }
757
758 public boolean isBlocked(final Jid jid) {
759 final var blocklist = getBlocklist();
760 return jid != null && blocklist.contains(jid.asBareJid());
761 }
762
763 public Set<Jid> getBlocklist() {
764 final var connection = this.xmppConnection;
765 if (connection == null) {
766 return Collections.emptySet();
767 }
768 return connection.getManager(BlockingManager.class).getBlocklist();
769 }
770
771 public boolean isOnlineAndConnected() {
772 return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
773 }
774
775 @Override
776 public int getAvatarBackgroundColor() {
777 return UIHelper.getColorForName(jid.asBareJid().toString());
778 }
779
780 @Override
781 public String getAvatarName() {
782 throw new IllegalStateException("This method should not be called");
783 }
784
785 public void setServiceOutageStatus(final ServiceOutageStatus sos) {
786 this.serviceOutageStatus = sos;
787 }
788
789 public ServiceOutageStatus getServiceOutageStatus() {
790 return this.serviceOutageStatus;
791 }
792
793 public boolean isServiceOutage() {
794 final var sos = this.serviceOutageStatus;
795 if (sos != null
796 && isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)
797 && ServiceOutageStatus.isPossibleOutage(this.status)) {
798 return sos.isNow();
799 }
800 return false;
801 }
802
803 public void setXmppConnection(final XmppConnection connection) {
804 this.xmppConnection = connection;
805 }
806
807 public enum State {
808 DISABLED(false, false),
809 LOGGED_OUT(false, false),
810 OFFLINE(false),
811 CONNECTING(false),
812 ONLINE(false),
813 NO_INTERNET(false),
814 CONNECTION_TIMEOUT,
815 UNAUTHORIZED,
816 TEMPORARY_AUTH_FAILURE,
817 SERVER_NOT_FOUND,
818 REGISTRATION_SUCCESSFUL(false),
819 REGISTRATION_FAILED(true, false),
820 REGISTRATION_WEB(true, false),
821 REGISTRATION_CONFLICT(true, false),
822 REGISTRATION_NOT_SUPPORTED(true, false),
823 REGISTRATION_PLEASE_WAIT(true, false),
824 REGISTRATION_INVALID_TOKEN(true, false),
825 REGISTRATION_PASSWORD_TOO_WEAK(true, false),
826 TLS_ERROR,
827 TLS_ERROR_DOMAIN,
828 CHANNEL_BINDING,
829 INCOMPATIBLE_SERVER,
830 INCOMPATIBLE_CLIENT,
831 TOR_NOT_AVAILABLE,
832 DOWNGRADE_ATTACK,
833 SESSION_FAILURE,
834 BIND_FAILURE,
835 HOST_UNKNOWN,
836 STREAM_ERROR,
837 SEE_OTHER_HOST,
838 STREAM_OPENING_ERROR,
839 POLICY_VIOLATION,
840 PAYMENT_REQUIRED,
841 MISSING_INTERNET_PERMISSION(false);
842
843 private final boolean isError;
844 private final boolean attemptReconnect;
845
846 State(final boolean isError) {
847 this(isError, true);
848 }
849
850 State(final boolean isError, final boolean reconnect) {
851 this.isError = isError;
852 this.attemptReconnect = reconnect;
853 }
854
855 State() {
856 this(true, true);
857 }
858
859 public boolean isError() {
860 return this.isError;
861 }
862
863 public boolean isAttemptReconnect() {
864 return this.attemptReconnect;
865 }
866
867 public int getReadableId() {
868 return switch (this) {
869 case DISABLED -> R.string.account_status_disabled;
870 case LOGGED_OUT -> R.string.account_state_logged_out;
871 case ONLINE -> R.string.account_status_online;
872 case CONNECTING -> R.string.account_status_connecting;
873 case OFFLINE -> R.string.account_status_offline;
874 case UNAUTHORIZED -> R.string.account_status_unauthorized;
875 case SERVER_NOT_FOUND -> R.string.account_status_not_found;
876 case NO_INTERNET -> R.string.account_status_no_internet;
877 case CONNECTION_TIMEOUT -> R.string.account_status_connection_timeout;
878 case REGISTRATION_FAILED -> R.string.account_status_regis_fail;
879 case REGISTRATION_WEB -> R.string.account_status_regis_web;
880 case REGISTRATION_CONFLICT -> R.string.account_status_regis_conflict;
881 case REGISTRATION_SUCCESSFUL -> R.string.account_status_regis_success;
882 case REGISTRATION_NOT_SUPPORTED -> R.string.account_status_regis_not_sup;
883 case REGISTRATION_INVALID_TOKEN -> R.string.account_status_regis_invalid_token;
884 case TLS_ERROR -> R.string.account_status_tls_error;
885 case TLS_ERROR_DOMAIN -> R.string.account_status_tls_error_domain;
886 case INCOMPATIBLE_SERVER -> R.string.account_status_incompatible_server;
887 case INCOMPATIBLE_CLIENT -> R.string.account_status_incompatible_client;
888 case CHANNEL_BINDING -> R.string.account_status_channel_binding;
889 case TOR_NOT_AVAILABLE -> R.string.account_status_tor_unavailable;
890 case BIND_FAILURE -> R.string.account_status_bind_failure;
891 case SESSION_FAILURE -> R.string.session_failure;
892 case DOWNGRADE_ATTACK -> R.string.sasl_downgrade;
893 case HOST_UNKNOWN -> R.string.account_status_host_unknown;
894 case POLICY_VIOLATION -> R.string.account_status_policy_violation;
895 case REGISTRATION_PLEASE_WAIT -> R.string.registration_please_wait;
896 case REGISTRATION_PASSWORD_TOO_WEAK -> R.string.registration_password_too_weak;
897 case STREAM_ERROR -> R.string.account_status_stream_error;
898 case STREAM_OPENING_ERROR -> R.string.account_status_stream_opening_error;
899 case PAYMENT_REQUIRED -> R.string.payment_required;
900 case SEE_OTHER_HOST -> R.string.reconnect_on_other_host;
901 case MISSING_INTERNET_PERMISSION -> R.string.missing_internet_permission;
902 case TEMPORARY_AUTH_FAILURE -> R.string.account_status_temporary_auth_failure;
903 default -> R.string.account_status_unknown;
904 };
905 }
906 }
907}