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