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