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