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 synchronized (gateways) {
666 for (final var k : new HashSet<>(gateways.keySet())) {
667 gateways.remove(k, contact);
668 }
669 for (final var p : contact.getPresences().getPresences()) {
670 final var disco = p.getServiceDiscoveryResult();
671 if (disco == null) continue;
672 for (final var identity : disco.getIdentities()) {
673 if ("gateway".equals(identity.getCategory())) {
674 gateways.put(identity.getType(), contact);
675 }
676 }
677 }
678 }
679 }
680
681 public Collection<Contact> getGateways(final String type) {
682 synchronized (gateways) {
683 return ImmutableList.copyOf(gateways.get(type));
684 }
685 }
686
687 public Collection<Bookmark> getBookmarks() {
688 synchronized (this.bookmarks) {
689 return ImmutableList.copyOf(this.bookmarks.values());
690 }
691 }
692
693 public boolean areBookmarksLoaded() {
694 // No way to tell if old PEP bookmarks are all loaded yet if they are empty
695 // because we don't manually fetch them...
696 if (getXmppConnection().getFeatures().bookmarksConversion()) return true;
697
698 return bookmarksLoaded;
699 }
700
701 public void setBookmarks(final Map<Jid, Bookmark> bookmarks) {
702 synchronized (this.bookmarks) {
703 this.bookmarks.clear();
704 this.bookmarks.putAll(bookmarks);
705 this.bookmarksLoaded = true;
706 }
707 }
708
709 public void putBookmark(final Bookmark bookmark) {
710 synchronized (this.bookmarks) {
711 this.bookmarks.put(bookmark.getJid(), bookmark);
712 }
713 }
714
715 public void removeBookmark(Bookmark bookmark) {
716 synchronized (this.bookmarks) {
717 this.bookmarks.remove(bookmark.getJid());
718 }
719 }
720
721 public void removeBookmark(Jid jid) {
722 synchronized (this.bookmarks) {
723 this.bookmarks.remove(jid);
724 }
725 }
726
727 public Set<Jid> getBookmarkedJids() {
728 synchronized (this.bookmarks) {
729 return new HashSet<>(this.bookmarks.keySet());
730 }
731 }
732
733 public Bookmark getBookmark(final Jid jid) {
734 synchronized (this.bookmarks) {
735 return this.bookmarks.get(jid.asBareJid());
736 }
737 }
738
739 public boolean setAvatar(final String filename) {
740 if (this.avatar != null && this.avatar.equals(filename)) {
741 return false;
742 } else {
743 this.avatar = filename;
744 return true;
745 }
746 }
747
748 public String getAvatar() {
749 return this.avatar;
750 }
751
752 public void activateGracePeriod(final long duration) {
753 if (duration > 0) {
754 this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
755 }
756 }
757
758 public void deactivateGracePeriod() {
759 this.mEndGracePeriod = 0L;
760 }
761
762 public boolean inGracePeriod() {
763 return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
764 }
765
766 public String getShareableUri() {
767 List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
768 String uri = "xmpp:" + Uri.encode(getJid().asBareJid().toEscapedString(), "@/+");
769 if (fingerprints.size() > 0) {
770 return XmppUri.getFingerprintUri(uri, fingerprints, ';');
771 } else {
772 return uri;
773 }
774 }
775
776 public String getShareableLink() {
777 List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
778 String uri =
779 "https://conversations.im/i/"
780 + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString());
781 if (fingerprints.size() > 0) {
782 return XmppUri.getFingerprintUri(uri, fingerprints, '&');
783 } else {
784 return uri;
785 }
786 }
787
788 private List<XmppUri.Fingerprint> getFingerprints() {
789 ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
790 if (axolotlService == null) {
791 return fingerprints;
792 }
793 fingerprints.add(
794 new XmppUri.Fingerprint(
795 XmppUri.FingerprintType.OMEMO,
796 axolotlService.getOwnFingerprint().substring(2),
797 axolotlService.getOwnDeviceId()));
798 for (XmppAxolotlSession session : axolotlService.findOwnSessions()) {
799 if (session.getTrust().isVerified() && session.getTrust().isActive()) {
800 fingerprints.add(
801 new XmppUri.Fingerprint(
802 XmppUri.FingerprintType.OMEMO,
803 session.getFingerprint().substring(2).replaceAll("\\s", ""),
804 session.getRemoteAddress().getDeviceId()));
805 }
806 }
807 return fingerprints;
808 }
809
810 public boolean isBlocked(final ListItem contact) {
811 final Jid jid = contact.getJid();
812 return jid != null
813 && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain()));
814 }
815
816 public boolean isBlocked(final Jid jid) {
817 return jid != null && blocklist.contains(jid.asBareJid());
818 }
819
820 public Collection<Jid> getBlocklist() {
821 return this.blocklist;
822 }
823
824 public void clearBlocklist() {
825 getBlocklist().clear();
826 }
827
828 public boolean isOnlineAndConnected() {
829 return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
830 }
831
832 @Override
833 public int getAvatarBackgroundColor() {
834 return UIHelper.getColorForName(jid.asBareJid().toString());
835 }
836
837 @Override
838 public String getAvatarName() {
839 throw new IllegalStateException("This method should not be called");
840 }
841
842 public enum State {
843 DISABLED(false, false),
844 LOGGED_OUT(false,false),
845 OFFLINE(false),
846 CONNECTING(false),
847 ONLINE(false),
848 NO_INTERNET(false),
849 UNAUTHORIZED,
850 TEMPORARY_AUTH_FAILURE,
851 SERVER_NOT_FOUND,
852 REGISTRATION_SUCCESSFUL(false),
853 REGISTRATION_FAILED(true, false),
854 REGISTRATION_WEB(true, false),
855 REGISTRATION_CONFLICT(true, false),
856 REGISTRATION_NOT_SUPPORTED(true, false),
857 REGISTRATION_PLEASE_WAIT(true, false),
858 REGISTRATION_INVALID_TOKEN(true, false),
859 REGISTRATION_PASSWORD_TOO_WEAK(true, false),
860 TLS_ERROR,
861 TLS_ERROR_DOMAIN,
862 INCOMPATIBLE_SERVER,
863 INCOMPATIBLE_CLIENT,
864 TOR_NOT_AVAILABLE,
865 DOWNGRADE_ATTACK,
866 SESSION_FAILURE,
867 BIND_FAILURE,
868 HOST_UNKNOWN,
869 STREAM_ERROR,
870 SEE_OTHER_HOST,
871 STREAM_OPENING_ERROR,
872 POLICY_VIOLATION,
873 PAYMENT_REQUIRED,
874 MISSING_INTERNET_PERMISSION(false);
875
876 private final boolean isError;
877 private final boolean attemptReconnect;
878
879 State(final boolean isError) {
880 this(isError, true);
881 }
882
883 State(final boolean isError, final boolean reconnect) {
884 this.isError = isError;
885 this.attemptReconnect = reconnect;
886 }
887
888 State() {
889 this(true, true);
890 }
891
892 public boolean isError() {
893 return this.isError;
894 }
895
896 public boolean isAttemptReconnect() {
897 return this.attemptReconnect;
898 }
899
900 public int getReadableId() {
901 switch (this) {
902 case DISABLED:
903 return R.string.account_status_disabled;
904 case LOGGED_OUT:
905 return R.string.account_state_logged_out;
906 case ONLINE:
907 return R.string.account_status_online;
908 case CONNECTING:
909 return R.string.account_status_connecting;
910 case OFFLINE:
911 return R.string.account_status_offline;
912 case UNAUTHORIZED:
913 return R.string.account_status_unauthorized;
914 case SERVER_NOT_FOUND:
915 return R.string.account_status_not_found;
916 case NO_INTERNET:
917 return R.string.account_status_no_internet;
918 case REGISTRATION_FAILED:
919 return R.string.account_status_regis_fail;
920 case REGISTRATION_WEB:
921 return R.string.account_status_regis_web;
922 case REGISTRATION_CONFLICT:
923 return R.string.account_status_regis_conflict;
924 case REGISTRATION_SUCCESSFUL:
925 return R.string.account_status_regis_success;
926 case REGISTRATION_NOT_SUPPORTED:
927 return R.string.account_status_regis_not_sup;
928 case REGISTRATION_INVALID_TOKEN:
929 return R.string.account_status_regis_invalid_token;
930 case TLS_ERROR:
931 return R.string.account_status_tls_error;
932 case TLS_ERROR_DOMAIN:
933 return R.string.account_status_tls_error_domain;
934 case INCOMPATIBLE_SERVER:
935 return R.string.account_status_incompatible_server;
936 case INCOMPATIBLE_CLIENT:
937 return R.string.account_status_incompatible_client;
938 case TOR_NOT_AVAILABLE:
939 return R.string.account_status_tor_unavailable;
940 case BIND_FAILURE:
941 return R.string.account_status_bind_failure;
942 case SESSION_FAILURE:
943 return R.string.session_failure;
944 case DOWNGRADE_ATTACK:
945 return R.string.sasl_downgrade;
946 case HOST_UNKNOWN:
947 return R.string.account_status_host_unknown;
948 case POLICY_VIOLATION:
949 return R.string.account_status_policy_violation;
950 case REGISTRATION_PLEASE_WAIT:
951 return R.string.registration_please_wait;
952 case REGISTRATION_PASSWORD_TOO_WEAK:
953 return R.string.registration_password_too_weak;
954 case STREAM_ERROR:
955 return R.string.account_status_stream_error;
956 case STREAM_OPENING_ERROR:
957 return R.string.account_status_stream_opening_error;
958 case PAYMENT_REQUIRED:
959 return R.string.payment_required;
960 case SEE_OTHER_HOST:
961 return R.string.reconnect_on_other_host;
962 case MISSING_INTERNET_PERMISSION:
963 return R.string.missing_internet_permission;
964 case TEMPORARY_AUTH_FAILURE:
965 return R.string.account_status_temporary_auth_failure;
966 default:
967 return R.string.account_status_unknown;
968 }
969 }
970 }
971}