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
800 && isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)
801 && ServiceOutageStatus.isPossibleOutage(this.status)) {
802 return sos.isNow();
803 }
804 return false;
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}