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