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