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